如何正确使用自定义shared_ptr删除器?

How to properly use the custom shared_ptr deleter?

对于 shared_ptr 使用自定义删除器的正确方法,我仍然有些困惑。我有一个跟踪资源分配的 ResourceManager class,我修改了它的接口以支持自动释放已用资源,方法是将 Release 方法设为私有,Allocate 方法返回一个 ResourceHolder:

// ResourceManager.cpp:
public:
    ResourceHolder<Resource> Allocate(args);

private:
    void Release(Resource*);

和 ResourceHolder class 我是这样实现的:

// ResourceHolder.h
template <typename T>
class ResourceHolder
{
public:
    ResourceHolder(
        _In_ T* resource,
        _In_ const std::function<void(T*)>& cleanupFunction)
        : _cleanupFunction(cleanupFunction)
        , _resource(resource, [&](T* resource)
        { 
            cleanup(resource); 
        }) // Uses a custom deleter to release the resource.
    {
    }

private:
    std::function<void(T*)> _cleanupFunction;
    std::shared_ptr<T> _resource;
};

// ResourceManager::Allocate()
...
return ResourceHolder<Resource>(new Resource(),[this](Resource* r) { Release(r); });
  1. 在我的清理方法中,我必须删除 T 吗?这样做总是安全的吗?

    if (nullptr != T) delete T;
    
  2. 如果 cleanup() 可以抛出异常会怎样?在某些情况下我可以让它脱离作用域,还是应该始终阻止它?

  3. 我的 ResourceManager 不依赖于我正在使用的跟踪库,因此我选择了一个回调,调用者可以通过其构造函数提供该回调,并将在发布方法中调用该回调.所以我的 Release 看起来像这样:

    void Release(Resource* r)
    {
        shared_ptr<std::Exception> exc = nullptr;
        try
        {
            // Do cleanup.
        }
        catch(Exception* ex)
        {
            exc.reset(ex);
        }
    
        if (nullptr != r) delete r;
    
        // Is it now safe to throw?
        if (nullptr != m_callback)
            m_callback(args, exc);
    }
    
    void Callback(args, shared_ptr<std::Exception> ex)
    {
        // Emit telemetry, including exception information.
    
        // If throwing here is ok, what is the correct way to throw exception here?
        if (nullptr != ex)
        {
            throw ex;
        }
    }
    

这是一种合理的设计方法吗?

In my cleanup method, do I have to delete T? Is it always safe to do it?

如果 指针引用了一个用 new 实例化的对象,那么您需要调用 delete 否则您将导致内存泄漏和未定义的行为。

What happens if cleanup() can throw an exception? Can I let it escape the scope under some circumstances, or should I always prevent it?

它不应该,您应该尽一切努力确保它不会。但是,如果清理代码 确实 抛出异常,您应该捕获它,适当地处理它,然后 吃掉 它。原因是可以在析构函数的上下文中调用自定义删除器,并且总是有可能在已经传播异常时调用析构函数。如果异常已经在进行中并且抛出另一个未捕获的异常,则应用程序将终止。换句话说,将自定义删除器和清理代码视为析构函数,并遵循有关异常处理的相同规则和准则。

Effective C++ 项目 #8 - 防止异常离开析构函数

Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program.

§ 15.1/7 C++ 标准 [except.throw]

If the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught, calls a function that exits via an exception, std::terminate is called.

-

Is this a sound design approach?

除了您目前打算如何处理异常,我看不出有什么问题。您需要做的唯一真正的改变是如何调用回调以及回调如何处理传递给它的异常。更改后的结果代码可能如下所示。

void Release(Resource* r)
{
    try
    {
        // Do cleanup.
    }
    catch (Exception& ex)
    {
        // Report to callback
        if (nullptr != m_callback)
            m_callback(args, &ex);

        // Handle exception completely or terminate

        // Done
        return;
    }

    // No exceptions, call with nullptr
    if (nullptr != m_callback)
        m_callback(args, nullptr);
}

void Callback(args, const Exception* ex)
{
    // Emit telemetry, including exception information.

    //  DO NOT RETHROW ex
}