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

Posted

技术标签:

【中文标题】如何正确使用自定义 shared_ptr 删除器?【英文标题】:How to properly use the custom shared_ptr deleter? 【发布时间】:2015-02-28 20:42:15 【问题描述】:

我仍然对使用带有 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); );

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

    if (nullptr != T) delete T;
    

    如果 cleanup() 可以抛出异常会发生什么?在某些情况下我可以让它逃脱范围,还是应该一直阻止它?

    我的 ResourceManager 不依赖于我正在使用的跟踪库,因此我选择了调用者可以通过其构造函数提供的回调,并将在 release 方法中调用该回调。所以我的 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;
        
    
    

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

【问题讨论】:

“这是一种合理的设计方法吗?” - 否。Release 可以在对象销毁的上下文中调用。由于异常可能已经在进行中,因此在此阶段发生的异常可能是一个主要问题。 但是将所有内容包装在 try catch 块中并将 Callback 设置为 nothow() 会好吗? 【参考方案1】:

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

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

如果 cleanup() 可以抛出异常会发生什么?在某些情况下我可以让它逃脱范围,还是应该一直阻止它?

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

Effective C++ Item #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

【讨论】:

以上是关于如何正确使用自定义 shared_ptr 删除器?的主要内容,如果未能解决你的问题,请参考以下文章

带有自定义删除器和分配器的 shared_ptr

[cpp] 使用 shared_ptr的自定义删除函数模拟 go 语言的 defer 功能

如何正确复制给定 shared_ptr 的对象

使用微调器从自定义列表视图中删除了错误的行

boost::shared_ptr与定制删除器

C++智能指针shared_ptr 定位删除器(仿函数)