Я все еще немного озадачен правильным способом использования пользовательского средства удаления с 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); });
В моем методе очистки я должен удалить T? Всегда ли это безопасно?
if (nullptr != T) delete T;
Что произойдет, если cleanup () может вызвать исключение? Могу ли я позволить ему выйти за рамки при некоторых обстоятельствах или я должен всегда предотвращать это?
Мой 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;
}
}
Это разумный подход к дизайну?
В моем методе очистки я должен удалить 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
}