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. 在我的清理方法中,我必须删除 T 吗?这样做总是安全的吗?

    if (nullptr != T) delete T;
    
  2. 如果 cleanup() 可以抛出异常会发生什么?在某些情况下我可以让它逃脱范围,还是应该总是阻止它?

  3. 我的 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;
        }
    }
    

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

4

1 回答 1

2

在我的清理方法中,我必须删除 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
}
于 2015-01-02T21:20:18.723 回答