Где правильная (обработка ресурсов) Правило нуля?
Вот статья, в которой говорится об идиоме под названием Rule of Zero.
Здесь выдержка:
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, поэтому вам не нужно заботиться о реализации сложной и многоплодной обертки из правила пяти.
Представленный таким образом (управляя ресурсами на основе ручек с помощью unique_ptr
, таким образом), это выглядит как взломать для меня, а не лучшее решение для того, что он пытается решить. Слишком много предположений подразумеваются неявно:
- Один из них способен сверять и использовать базовый тип, на который построен
#define
(или typedef
) HANDLE
. Для меня это должно быть скрытое знание, и решение должно основываться исключительно на том, что делает интерфейс: HANDLE
.
- Ручки могут быть любыми, они не должны быть типами указателей.
Я бы хотел использовать эту идиому, но во многих ситуациях я наткнулся на нее.
Является ли эта обертка, сфокусированная на RAII, уже сделанной и полезной в какой-нибудь классной библиотеке? Все ли используют такой инструмент, и я не знаю? (Я считаю, что неплохо иметь такую оснастку не только для одного, но и для многих типов владельцев)
РЕДАКТИРОВАТЬ 1
Речь идет не о дескрипторах ресурсов платформы, например, glGenLists
возвращает вид дескриптора, a GLuint
, и вы должны называть glDeleteLists
на нем. Как уже указывалось, ручками ресурсов не являются типы указателей, и это предположение не следует принимать.
EDIT 2
Правило Zero, в прежнем примере, с использованием уже существующего инструмента, unique_ptr
, показывает как хороший ярлык для управления дескрипторами. Дополнительные предположения, которые он требует, заставляют его отстать. Правильные предположения состоят в том, что у вас есть дескриптор, и у вас есть функция уничтожения ресурсов, которая уничтожает ресурс, заданный дескриптором. Независимо от того, является ли дескриптор void *
, a GLuint
, что бы это ни было, это не имеет значения, и, что еще хуже, не нужно даже требовать сопоставить внутренний тип HANDLE
. Для управления ручками RAII, если идиома говорит об этом хорошо и не может применяться в таких ситуациях, я считаю, что это не хорошо для этого, наконец, с данным инструментом.
РЕДАКТИРОВАТЬ 3
Иллюстративная ситуация была бы, скажем, вы отвечаете за использование новой сторонней библиотеки C. Он содержит FooHandle create_foo()
и a void destroy_foo(FooHandle)
. Итак, вы думаете, "позвольте использовать FooHandle, используя правило нуля". Хорошо, вы идете с помощью unique_ptr
, a unique_ptr
к тому, что вы спрашиваете сами? Является FooHandle
указателем?, так что вы используете unique_ptr
to FooHandle
не подвергаемый базовый тип? Это int
?, так что вы можете использовать его прямо или лучше (re) typedef
вещи, подобные @NicolBolas, сделали в его ответе. Для меня это кажется явным, даже в такой тривиальной ситуации, unique_ptr
уже показывает, что он не идеально подходит для управления ручками ресурсов.
Отказ от ответственности:
Я попытался переформулировать и лучше выразить себя:
EDIT 4
Я нашел то, что искал, я поставил его как ответ в переформулированном вопросе: qaru.site/info/404574/....
Ответы
Ответ 1
Фон: Правило нуля
В первую очередь, эта статья посвящена гораздо более общей идее, чем только пример оболочки обложки ресурса, о которой он упоминает.
Дело в том, что всякий раз, когда класс содержит членов с "нетривиальной" семантикой собственности, класс должен не отвечать за механику за обеспечение правильной семантики значений ( копировать, назначать, перемещать, разрушать) содержащего класса. Скорее, каждый компонент должен сам реализовать "Правило-3 +", если это необходимо, так что составной класс может использовать специальные элементы, не соответствующие стандарту компилятора.
Кроме того, новые стандартные типы интеллектуальных указателей значительно упрощают задачу обеспечения этого для содержащихся типов. В большинстве случаев членами, которые требовали внимания, были бы указатели: std:: unique_ptr, shared_ptr, weak_ptr адресуют потребности здесь.
Для пользовательских типов ресурсов вам может понадобиться написать класс оболочки Rule-Of-3 +, но только один раз. И остальная часть ваших классов будет пользоваться Правилом Зеро.
Пули:
- Один из них способен сверять и использовать базовый тип, на котором строится РУЧКА #define (или typedef). Для меня это должно быть скрытое знание, и решение должно основываться исключительно на том, что делает интерфейс, HANDLE.
A: 90% времени, когда вы можете (и должны) использовать std::unique_ptr<>
с пользовательским делетером. Знания и ответственность за очистку по-прежнему связаны с распределителем. Как это должно быть.
В остальных случаях вам придется написать один класс-оболочку для вашего конкретного ресурса, который в противном случае не поддерживается.
- Ручки могут быть любыми, они не должны быть типами указателей.
Они могут. Вы бы посмотрели, например. повышение:: необязательными. Или напишите эту обертку. Дело в том, что вы хотите выделить изолированный ресурс. Вы не хотите усложнять класс, который имеет собственный/содержащий такой ресурс.
Ответ 2
Является ли эта обертка, сфокусированная на 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 вокруг типа, предоставляющая не что иное, как доступ к ней и перемещение поддержки. Поэтому подавляющему большинству вашего кода не нужны явные конструкторы copy/move; только те немногие листовые классы.