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

4

我对使用自定义删除器与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. In my cleanup method, do I have to delete T? Is it always safe to do it?

    if (nullptr != T) delete T;
    
  2. What happens if cleanup() can throw an exception? Can I let it escape the scope under some circumstances, or should I always prevent it?

  3. My ResourceManager does not have a dependency on a tracing library I'm using, so I opted for a callback which a caller can provide through its constructor, and which will get called in the release method. So my Release looks something like this:

    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 可以在对象销毁的上下文中被调用。由于异常可能已经在进行中,因此在此阶段发生异常可能会导致严重问题。 - Captain Obvlious
但是将所有内容都包装在try catch块中,并使回调函数成为nothow(),这样做可以吗? - Dejan Nikolic
1个回答

2
在我的清理方法中,我是否需要删除T?这样做总是安全的吗?
如果指针引用使用new实例化的对象,则需要调用delete,否则会导致内存泄漏和未定义行为。
如果cleanup()可能会抛出异常会发生什么?在某些情况下,我可以让它逃脱作用域吗,还是应该始终防止它?
它不应该抛出异常,您应该尽一切努力确保它不会抛出异常。但是,如果清理代码确实抛出异常,则应捕获它,适当处理它,并将其“吞噬”。原因是自定义删除器可以在析构函数的上下文中调用,而析构函数正在被调用时,有可能已经传播了异常。如果已经存在异常并且抛出了另一个未捕获的异常,则应用程序将终止。换句话说,将自定义删除器和清理代码视为析构函数,并遵循关于异常处理的相同规则和指南。
Effective C++ Item #8-防止异常离开析构函数
析构函数不应该抛出异常。如果在析构函数中调用的函数可能会抛出异常,则析构函数应该捕获任何异常,然后忽略它们或终止程序。
C++标准§ 15.1/7 [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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接