Ответ 1
Существует простой способ использования RAII для управления ресурсами из интерфейса C-стиля: стандартные интеллектуальные указатели библиотеки, которые представлены в двух вариантах: std::unique_ptr
для ресурсов с одним владельцем и командой std::shared_ptr
и std::weak_ptr
для общих ресурсов. Если у вас возникли проблемы с выбором вашего ресурса, этот Q & A должен помочь вам решить. Доступ к необработанному указателю, который управляет интеллектуальный указатель, так же просто, как вызов его функции get
.
Если вы хотите простое управление ресурсами на основе областей, std::unique_ptr
- отличный инструмент для работы. Он предназначен для минимальных накладных расходов и легко настраивается для использования пользовательской логики уничтожения. Так просто, что вы можете это сделать, когда объявляете переменную ресурса:
#include <memory> // allow use of smart pointers
struct CStyleResource; // c-style resource
// resource lifetime management functions
CStyleResource* acquireResource(const char *, char*, int);
void releaseResource(CStyleResource* resource);
// my code:
std::unique_ptr<CStyleResource, decltype(&releaseResource)>
resource{acquireResource("name", nullptr, 0), releaseResource};
acquireResource
выполняется там, где вы его вызываете, в начале жизненного цикла переменной. releaseResource
будет выполняться в конце жизненного цикла переменной, обычно, когда он выходит из области видимости. 1 Не верьте мне? вы можете видеть его в действии на Coliru, где я предоставил некоторые фиктивные реализации для функций получения и выпуска, чтобы вы могли увидеть, как это происходит.
Вы можете сделать то же самое с std::shared_ptr
, если вам потребуется эта марка ресурса ресурса:
// my code:
std::shared_ptr<CStyleResource>
resource{acquireResource("name", nullptr, 0), releaseResource};
Теперь все они хорошо и хорошо, но стандартная библиотека имеет std::make_unique
2 и std::make_shared
, и одна из причин является дополнительной безопасностью.
GotW # 56 упоминает, что оценка аргументов функции неупорядочена, а это означает, что если у вас есть функция, которая берет ваш новый std::unique_ptr
и некоторый ресурс, который может вызывать конструкцию, снабжая этот ресурс вызову функции следующим образом:
func(
std::unique_ptr<CStyleResource, decltype(&releaseResource)>{
acquireResource("name", nullptr, 0),
releaseResource},
ThrowsOnConstruction{});
означает, что инструкции могут быть упорядочены следующим образом:
- вызов
acquireResource
- Конструкция
ThrowsOnConstruction
- построить
std::unique_ptr
из указателя ресурсов
и что наш ценный ресурс интерфейса C не будет очищен должным образом, если будет сделан шаг 2.
Опять же, как упоминалось в GotW # 56, на самом деле существует относительно простой способ справиться с проблемой безопасности исключений. В отличие от оценок выражений в аргументах функций, оценки функций не могут чередоваться. Поэтому, если мы приобретем ресурс и передадим его unique_ptr
внутри функции, мы гарантируем, что не будет сложного бизнеса, чтобы утечка нашего ресурса, когда ThrowsOnConstruction
бросает на конструкцию. Мы не можем использовать std::make_unique
, потому что он возвращает std::unique_ptr
с дефолтом по умолчанию, и мы хотим, чтобы наш собственный пользовательский аромат deleter. Мы также хотим указать нашу функцию сбора ресурсов, поскольку она не может быть выведена из типа без дополнительного кода. Реализация такой вещи достаточно проста с мощью шаблонов: 3
#include <memory> // smart pointers
#include <utility> // std::forward
template <
typename T,
typename Deletion,
typename Acquisition,
typename...Args>
std::unique_ptr<T, Deletion> make_c_handler(
Acquisition acquisition,
Deletion deletion,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), deletion};
}
вы можете использовать его следующим образом:
auto resource = make_c_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0);
и вызовите func
без проблем, например:
func(
make_c_handler<CStyleResource(
acquireResource, releaseResource, "name", nullptr, 0),
ThrowsOnConstruction{});
Компилятор не может взять конструкцию ThrowsOnConstruction
и придерживаться ее между вызовом acquireResource
и конструкцией unique_ptr
, поэтому вы хорошо.
эквивалент shared_ptr
аналогичен простому: просто замените возвращаемое значение std::unique_ptr<T, Deletion>
на std::shared_ptr<T>
и измените имя, чтобы указать общий ресурс: 4
template <
typename T,
typename Deletion,
typename Acquisition,
typename...Args>
std::shared_ptr<T> make_c_shared_handler(
Acquisition acquisition,
Deletion deletion,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), deletion};
}
Использование снова похоже на версию unique_ptr
:
auto resource = make_c_shared_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0);
и
func(
make_c_shared_handler<CStyleResource(
acquireResource, releaseResource, "name", nullptr, 0),
ThrowsOnConstruction{});
Edit:
Как уже упоминалось в комментариях, можно сделать еще один шаг к использованию std::unique_ptr
: указать механизм удаления во время компиляции, чтобы unique_ptr
не нужно было переносить указатель на объект, если он перемещался по программе. Для создания шаблона, не использующего stateless, для используемого указателя функции требуется четыре строки кода, помещенные перед make_c_handler
:
template <typename T, void (*Func)(T*)>
struct CDeleter{
void operator()(T* t){Func(t);}
};
Затем вы можете изменить make_c_handler
так:
template <
typename T,
void (*Deleter)(T*),
typename Acquisition,
typename...Args>
std::unique_ptr<T, CDeleter<T, Deleter>> make_c_handler(
Acquisition acquisition,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), {}};
}
Синтаксис использования затем немного меняется, на
auto resource = make_c_handler<CStyleResource, releaseResource>(
acquireResource, "name", nullptr, 0);
make_c_shared_handler
не будет изменяться на шаблонный дебетер, так как shared_ptr
не несет информацию об отправителе, доступную во время компиляции.
1. Если значение интеллектуального указателя nullptr
при его разрушении, оно не будет вызывать связанную функцию, что довольно хорошо для библиотек, которые обрабатывают вызовы с освобождением ресурсов с нулевыми указателями в качестве условий ошибки, например SDL. < ш > 2. std::make_unique
был включен только в библиотеку на С++ 14, поэтому, если вы используете С++ 11, вы можете реализовать свой собственный - это очень полезно, даже если это не совсем то, что вы хотите здесь.
3. Эта (и реализация std::make_unique
, связанная в 2) зависит от вариативных шаблонов. Если вы используете VS2012 или VS2010, которые имеют ограниченную поддержку С++ 11, у вас нет доступа к вариационным шаблонам. Реализация std::make_shared
в этих версиях была создана с индивидуальными перегрузками для каждого номера аргумента и комбинации специализаций. Сделайте то, что хотите.
4. std::make_shared
на самом деле имеет более сложные механизмы, чем это, но для этого требуется знание того, насколько велика будет объект типа. У нас нет этой гарантии, так как мы работаем с интерфейсом в стиле C и можем иметь только декларацию прямого типа нашего ресурса, поэтому мы не будем беспокоиться об этом здесь.