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

  3. Мой ResourceManager не имеет зависимости от используемой библиотеки трассировки, поэтому я выбрал обратный вызов, который вызывающий может предоставить через конструктор, и который будет вызываться в методе 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;
    }
    }
    

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

3

Решение

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

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

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

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

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

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

§ 15.1 / 7 C ++ Standard [кроме. 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
}
1

Другие решения


По вопросам рекламы [email protected]