Как правильно использовать пользовательское средство удаления shared_ptr?

Я все еще немного не понимаю, как правильно использовать пользовательское средство удаления с 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. Что произойдет, если функция очистки() может вызвать исключение? Могу ли я позволить ему выйти за рамки при некоторых обстоятельствах, или я должен всегда предотвращать это?

  3. Мой ResourceManager не зависит от библиотеки трассировки, которую я использую, поэтому я выбрал обратный вызов, который вызывающая сторона может предоставить через свой конструктор и который будет вызываться в методе выпуска. Итак, мой релиз выглядит примерно так:

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

Это подход к звуковому дизайну?


person Dejan Nikolic    schedule 02.01.2015    source источник
comment
Это правильный подход к дизайну? - № Release можно вызывать в контексте уничтожения объектов. Поскольку исключение может уже находиться в процессе, возникновение исключения на этом этапе может стать серьезной проблемой.   -  person Captain Obvlious    schedule 02.01.2015
comment
Но было бы нормально обернуть все в блок try catch и сделать Callback nothow()?   -  person Dejan Nikolic    schedule 02.01.2015


Ответы (1)


В моем методе очистки мне нужно удалить T? Всегда ли это безопасно?

Если указатель ссылается на объект, созданный с помощью new, вам нужно вызвать delete, иначе вы получите утечку памяти и неопределенное поведение.

Что произойдет, если функция очистки() может вызвать исключение? Могу ли я позволить ему выйти за рамки при некоторых обстоятельствах, или я должен всегда предотвращать это?

Этого не должно быть, и вы должны приложить все усилия, чтобы этого не произошло. Однако, если код очистки выдает исключение, вы должны перехватить его, обработать соответствующим образом и съесть. Причина в том, что пользовательское средство удаления может быть вызвано в контексте деструктора, и всегда есть вероятность, что деструктор вызывается, когда исключение уже распространяется. Если исключение уже выполняется и возникает другое необработанное исключение, приложение завершится. Другими словами, относитесь к пользовательскому коду удаления и очистки как к деструктору и следуйте тем же правилам и рекомендациям в отношении обработки исключений.

Эффективный C++ Пункт № 8. Предотвращение выхода исключений из деструкторов

Деструкторы никогда не должны генерировать исключения. Если функции, вызываемые в деструкторе, могут генерировать ошибки, деструктор должен перехватывать любые исключения, затем проглатывать их или завершать программу.

§ 15.1/7 Стандарт C++ [кроме.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
}
person Captain Obvlious    schedule 02.01.2015