How to use the custom shared_ptr deblocker correctly?

I'm still a little confused about the correct way to use a custom deleter with shared_ptr. I have a ResourceManager class that keeps track of resource allocation and I modified its interface to support automatic release of used resources by making the Release method private and the Allocate method returning a ResourceHolder:

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

private:
    void Release(Resource*);

      

And the ResourceHolder class I am implemented like this:

// 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); });

      

  • In my cleanup method, do I need to remove T? Is it always safe?

    if (nullptr != T) delete T;
    
          

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

  • My ResourceManager doesn't have a dependency on the tracing library in use, so I chose a callback that the caller can provide through its constructor and that will be 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;
        }
    }
    
          

Is this approach sane design?

+3


source to share


1 answer


In my cleanup method, should I remove T? Is it always safe?

If the pointer refers to an object created with new

you need to call delete

otherwise you will get a memory leak and undefined behavior.

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

It shouldn't, and you should do your best to keep it from happening. However, if the cleanup code throws an exception, you must catch it, handle it appropriately, and eat it. The reason is that a custom remover can be called in the context of a destructor, and there is always the possibility that the destructor is called when the exception is already propagating. If the exception is already running and another unhandled exception is thrown, the application will terminate. In other words, treat your custom delete and cleanup code as if it were a destructor, and follow the same rules and best practices for handling exceptions.

Effective C ++ Item # 8 - Prevent exceptions from destructors

Destructors should never throw exceptions. If functions called in the destructor can throw, the destructor must catch any exceptions, then internalize them, or terminate the program.



ยง 15.1 / 7 C ++ Standard [except cast]

If the exception handling engine, after completing the evaluation of the expression that should be thrown, but before the exception is caught, calls the function that exits through the exception is called std::terminate

.

-

Is this design sensible?

Except for how you are going to handle exceptions, I see nothing wrong with that. The only real changes you need to make are how you call the callback and how the callback handles the exception passed to it. The resulting code after the changes might look something like this:

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
}

      

+1


source







All Articles