Я работаю с устаревшим API C, в котором приобретение какого-то ресурса дорого и освобождает этот ресурс абсолютно критично. Я использую С++ 14, и я хочу создать класс для управления этими ресурсами. Вот основной скелет вещи...
class Thing
{
private:
void* _legacy;
public:
void Operation1(...);
int Operation2(...);
string Operation3(...);
private:
Thing(void* legacy) :
_legacy(legacy)
{
}
};
На самом деле это не одноэлементный шаблон. Ничто не является статичным, и может быть много экземпляров Thing
, все управляющие своими собственными устаревшими ресурсами. Кроме того, это не просто умный указатель. Оболоченный указатель _legacy
является закрытым, и все операции отображаются через некоторые функции публичного экземпляра, которые скрывают унаследованный API от потребителей.
Конструктор является закрытым, потому что экземпляры Thing
будут возвращены из статического factory или named-constructor, который фактически приобретет ресурс. Вот дешевая имитация этого factory, используя malloc()
в качестве места для кода, который будет вызывать устаревший API...
public:
static Thing Acquire()
{
// Do many things to acquire the thing via the legacy API
void* legacy = malloc(16);
// Return a constructed thing
return Thing(legacy);
}
Вот деструктор, который отвечает за освобождение устаревшего ресурса, снова free()
является просто заполнителем...
~Thing() noexcept
{
if (nullptr != _legacy)
{
// Do many things to free the thing via the legacy API
// (BUT do not throw any exceptions!)
free(_legacy);
_legacy = nullptr;
}
}
Теперь я хочу убедиться, что ровно один старый ресурс управляется одним экземпляром Thing
. Я не хотел, чтобы пользователи класса Thing
проходили экземпляры по желанию - они должны либо принадлежать локально к классу или функции, либо напрямую, либо через unique_ptr
, либо завернуты с помощью shared_ptr
, которые могут быть переданы, С этой целью я удалил оператор присваивания и конструкторы копирования...
private:
Thing(Thing const&) = delete;
void operator=(Thing const&) = delete;
Однако это добавило дополнительную проблему. Либо мне пришлось изменить мой метод factory, чтобы вернуть unique_ptr<Thing>
или shared_ptr<Thing>
, или мне пришлось реализовать семантику перемещения. Я не хотел диктовать шаблон, под которым следует использовать Thing
, поэтому я решил добавить оператор move-move и move-assign-operator следующим образом:
Thing(Thing&& old) noexcept : _legacy(old._legacy)
{
// Reset the old thing state to reflect the move
old._legacy = nullptr;
}
Thing& operator= (Thing&& old) noexcept
{
if (&old != this)
{
swap(_legacy, old._legacy);
}
return (*this);
}
С этим все сделано, я мог бы использовать Thing
как локальный и переместить его...
Thing one = Thing::Acquire();
Thing two = move(one);
Я не мог сломать шаблон, пытаясь выполнить самоопределение:
Thing one = Thing::Acquire();
one = one; // Build error!
Я мог бы сделать unique_ptr
для одного...
auto three = make_unique<Thing>(Thing::Acquire());
Или a shared_ptr
...
auto three = make_shared<Thing>(Thing::Acquire());
Все работало так, как я ожидал, и мой деструктор работал в нужный момент во всех моих тестах. Фактически, единственное раздражение заключалось в том, что make_unique
и make_shared
оба фактически вызывали конструктор move - он не был оптимизирован, как я надеялся.
Первый вопрос: Я правильно выполнил оператор move-constructor и move-assign-operator? (Они довольно новы для меня, и это будет первый раз, когда я использовал его в гневе.)
Второй вопрос: Прокомментируйте этот шаблон! Это хороший способ обернуть устаревшие ресурсы в классе С++ 14?
Наконец: Должен ли я что-либо изменить, чтобы код был лучше, быстрее, проще или читабельнее?