Упрощаем управление ресурсами устаревших API с применением умных указателей
Умные указатели (unique_ptr
shared_ptr
и weak_ptr
) очень полезны, и можно сказать, что программист должен Но если для объекта нельзя выделить память с помощью оператора new
delete
? Во многих устаревших библиотеках есть собственные функции выделения/удаления памяти. Кажется, это может стать проблемой, поскольку мы узнали, что умные указатели полагаются на операторы new
и delete
. Если создание и/или разрушение конкретных типов объектов полагается на конкретные интерфейсы удаления фабричных функций, не помешает ли это получить огромное преимущество от использования умных указателей?Вовсе нет. В этом разделе мы увидим, что нужно лишь немного изменить умные указатели, чтобы позволить им следовать определенным процедурам выделения и удаления памяти для конкретных объектов.
Как это делается
В данном примере мы определим тип, для которого нельзя непосредственно выделить память с помощью оператора new и нельзя освободить ее, прибегнув к оператору delete
unique_ptr
и smart_ptr
.1. Как и всегда, сначала включим необходимые заголовочные файлы и объявим об использовании пространства имен std
#include
#include
#include
using namespace std;
2. Далее объявим класс, конструктор и деструктор которого имеют модификатор private
class Foo
{
string name;
Foo(string n)
: name{n}
{ cout << "CTOR " << name << '\n'; }
~Foo() { cout << "DTOR " << name << '\n';}
3. Статические методы create_foo
destroy_foo
будут создавать и удалять экземпляры типа Fo
o. Они работают с необработанными указателями. Это симулирует ситуацию, возникающую при использовании устаревшего API языка С, который не дает задействовать их непосредственно для обычных указателей shared_p
tr:public:
static Foo* create_foo(string s) {
return new Foo{move(s)};
}
static void destroy_foo(Foo *p) { delete p; }
};
4. Теперь сделаем так, чтобы подобными объектами можно было управлять с помощью shared_ptr
create_foo
, в конструктор общего указателя. Разрушение объекта выглядит сложнее, поскольку функция удаления класса shared_ptr
, использующаяся по умолчанию, решит проблему неправильно. Идея заключается в том, что можно задать для класса shared_ptr
destroy_foo
. Если функция, которую нужно вызвать для разрушения объекта, более сложна, то можно обернуть ее в лямбда-выражение.static shared_ptr
{
return {Foo::create_foo(move(s)), Foo::destroy_foo};
}
5. Обратите внимание: make_shared_foo
shared_ptr
, поскольку передача пользовательской функции удаления не изменяет ее тип. Это произошло потому, что shared_ptr
применяет вызовы виртуальных функций для сокрытия таких деталей. Уникальные указатели не создают наличных издержек; это не дает задействовать для них подобный прием. Нужно изменить тип unique_ptr
. В качестве второго шаблонного параметра мы передадим экземпляр типа void (*)(Foo*)
, данный тип имеет и указатель на функцию destroy_foo
:static unique_ptr
{
return {Foo::create_foo(move(s)), Foo::destroy_foo};
}
6. В функции main
int main()
{
auto ps (make_shared_foo("shared Foo instance"));
auto pu (make_unique_foo("unique Foo instance"));
}
7. Компиляция и запуск программы дадут ожидаемый результат:
$ ./legacy_shared_ptr
CTOR shared Foo instance
CTOR unique Foo instance