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

时间:2015-01-02 14:09:46

标签: c++ shared-ptr resource-management

我仍然对使用shared_ptr的自定义删除器的正确方法感到困惑。我有一个ResourceManager类来跟踪资源分配,我修改了它的接口以支持通过使Release方法私有来自动释放已使用的资源,并且Allocate方法返回一个ResourceHolder:

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

private:
    void Release(Resource*);

我实现的ResourceHolder类如下:

// 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;
        }
    }
    
  4. 这是一种合理的设计方法吗?

1 个答案:

答案 0 :(得分:1)

  

在我的清理方法中,我是否必须删除T?这样做总是安全的吗?

如果指针引用了用new实例化的对象,则需要调用delete,否则最终会导致内存泄漏和未定义的行为。

  

如果cleanup()可以抛出异常会怎样?在某些情况下,我可以让它逃避范围,还是应该总是阻止它?

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

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

  

析构函数永远不会发出异常。如果在析构函数中调用的函数可能抛出,则   析构函数应该捕获任何异常,然后吞下它们或终止程序。

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

  

如果异常处理机制在完成对要抛出的表达式的评估之后但在捕获异常之前调用了一个通过异常退出的函数,则调用std::terminate

-

  

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

除了您目前打算如何处理异常外,我认为它没有任何问题。您需要做的唯一真正的更改是如何调用回调以及回调如何处理传递给它的异常。更改后生成的代码可能如下所示。

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
}