Вот статья, в которой говорится о идиоме Правило нуля.
Вот выдержка:
class module {
public:
explicit module(std::wstring const& name)
: handle { ::LoadLibrary(name.c_str()), &::FreeLibrary } {}
// other module related functions go here
private:
using module_handle = std::unique_ptr<void, decltype(&::FreeLibrary)>;
module_handle handle;
};
Это повторно unique_ptr
Функции RAII, так что вам не нужно заботиться о реализации сложной и многословной обертки Rule of Five.
Представлено так (управление дескрипторными ресурсами с помощью unique_ptr
таким образом), это выглядит как хак для меня, не лучшее решение для того, что он пытается решить. Слишком много предположений неявно предполагается:
#define
(или же typedef
) HANDLE
построен на. Для меня это должны быть скрытые знания, а решение должно основываться исключительно на том, что делает интерфейс доступным: HANDLE
,Я хотел бы использовать эту идиому, но во многих ситуациях мне не удается ее найти.
Это сфокусированный на RAII обертке уже сделано и может быть использовано в какой-нибудь классной библиотеке? Все используют такой инструмент, а я не в курсе? (Я думаю, что было бы хорошо иметь такой набор инструментов не только для одного, но и для многих типов собственности)
Это не относится к дескрипторам ресурсов для конкретной платформы, например, glGenLists
возвращает своего рода ручку, это GLuint
и вам следует позвонить glDeleteLists
в теме. Как уже говорилось, указатели ресурсов не обязательно должны быть указателями, и это предположение не следует принимать.
Правило нуля, в первом примере, используя уже существующий инструмент, unique_ptr
, показывает как хороший ярлык для управления дескриптором. Дополнительные допущения, необходимые для этого, делают его несостоятельным. Правильные предположения, что у вас есть справиться и у вас есть функция уничтожения ресурсов который уничтожает ресурс, данный справиться. Является ли ручка void *
, GLuint
что бы то ни было, это не должно иметь значения, и, что еще хуже, не нужно даже быть равным HANDLE
внутренний тип Для целей управления дескрипторами RAII, если идиома говорит, что это хорошо для этого, и не может применяться в таких ситуациях, я чувствую, что это не хорошо для этого, наконец, с помощью данного инструмента.
В качестве примера можно привести, скажем, ответственность за использование свежей сторонней библиотеки C. Он содержит FooHandle create_foo()
и void destroy_foo(FooHandle)
, Итак, вы думаете: «Давайте использовать FooHandle, используя правило нуля». Хорошо, вы идете с помощью unique_ptr
, unique_ptr
к чему ты себя спрашиваешь? Является FooHandle
указатель ?, так что вы используете unique_ptr
в FooHandle
«s не подвергается основной тип? Это int
?, так что вы можете использовать его прямо, или лучше (пере)typedef
такие вещи, как @NicolBolas сделал в своем ответе. Мне кажется, что даже в такой тривиальной ситуации unique_ptr
уже показывает как не идеальный подходит для управления дескрипторами ресурсов.
Отказ от ответственности:
Я попытался переформулировать и лучше выразить себя в:
Я нашел то, что искал, я сформулировал это как ответ на переформулированный вопрос: https://stackoverflow.com/a/14902921.
Прежде всего, эта статья о гораздо более общей идее, чем просто пример обертки дескриптора ресурса, о котором она упоминает.
Дело в том, что всякий раз, когда класс содержит члены с «нетривиальной» семантикой владения, класс должен не быть ответственным за механика обеспечения правильной семантики значения (копировать. назначать, перемещать, уничтожать) содержащего класса. Скорее, каждый компонент должен сам реализовывать «Правило-3 +» в зависимости от ситуации, чтобы составной класс мог использовать специальные члены по умолчанию для компилятора.
Более того, новые стандартные типы интеллектуальных указателей значительно упрощают задачу реализации этого для содержащихся типов. В большинстве случаев члены, которым требовалось внимание, были бы указателями: std :: unique_ptr, shared_ptr, weak_ptr удовлетворяют потребности здесь.
Для пользовательских типов ресурсов вам может потребоваться написать класс-оболочку Rule-Of-3 +, но только один раз. А остальные ваши классы получат выгоду от правила нуля.
Пули:
- Один из них может использовать и использовать базовый тип, на котором построен HANDLE #define (или typedef). Для меня это должны быть скрытые знания, а решение должно основываться исключительно на том, что делает интерфейс доступным, HANDLE.
A: 90% времени вы можете (и должны) использовать std::unique_ptr<>
с пользовательским удалителем. Знание и ответственность за очистку по-прежнему лежит на распределителе. Так и должно быть.
В остальных случаях вам придется написать один класс-оболочку для вашего конкретного ресурса, который не поддерживается в противном случае.
- Дескрипторы могут быть чем угодно, они не обязательно должны быть указателями.
Они могут. Вы бы посмотрели, например, на повышение :: необязательными. Или напишите эту обертку. Дело в том, что вы хотите это изолированный за ресурс. Вы не хотите усложнять класс, который владеет / содержит такой ресурс.
Эта оболочка RAII, сфокусированная на ручке, уже сделана и может использоваться в какой-нибудь классной библиотеке?
Для вашего случая это называется unique_ptr
, Заметим:
struct WndLibDeleter
{
typedef HANDLE pointer;
void operator()(HANDLE h) {::FreeLibrary(h);}
};
using WndLibrary = std::unique_ptr<HANDLE, WndLibDeleter>;
WndLibrary LoadWndLibrary(std::wstring const& name)
{
return WndLibrary(::LoadLibrary(name.c_str()));
}
С удалителями, unique_ptr
может обслуживать магазин и обслуживать любые объекты, которые являются NullablePointer. HANDLE
является NullablePointer, так что вы можете обслуживать его.
Для объектов, которые не являются NullablePointers, вам придется использовать что-то еще. Смысл Правила Ноля состоит в том, чтобы сделать «что-то еще» как можно меньше. Это будет не более чем RAII-оболочка вокруг типа, обеспечивающая не что иное, как доступ к нему и поддержку перемещения. Поэтому подавляющему большинству вашего кода не нужны явные конструкторы копирования / перемещения; только те несколько листовых классов.