Управляйте ресурсами автоматически, используя дескрипторы ресурсов и RAII
Причина
Чтобы избежать утечек и сложности ручного управления ресурсами. Принудительная симметрия конструктора и деструктора в C++ отражает симметрию, присущую парам функций получения/освобождения ресурсов, таким как fopen/fclose, lock/unlock и new/delete. Когда вы работаете с ресурсом, требующим парных вызовов получения/освобождения, инкапсулируйте его в объект, который обеспечивает это сопряжение: получайте ресурс в конструкторе и освобождайте в деструкторе.
Пример (плохой)
Рассмотрим:
void send(X* x, string_view destination)
{
auto port = open_port(destination);
my_mutex.lock();
// ...
send(port, x);
// ...
my_mutex.unlock();
close_port(port);
delete x;
}
В этом коде нужно помнить о вызовах unlock, close_port и delete на каждом пути выполнения, причём ровно по одному разу. Более того, если в части кода, обозначенной ..., выброшено исключение, x утечёт, а my_mutex останется заблокированным.
Пример
Рассмотрим:
void send(unique_ptr<X> x, string_view destination) // x владеет объектом X
{
Port port{destination}; // port владеет PortHandle
lock_guard<mutex> guard{my_mutex}; // guard владеет блокировкой
// ...
send(port, x);
// ...
} // автоматически снимает блокировку my_mutex и удаляет указатель в x
Теперь весь код освобождения ресурсов выполняется автоматически, один раз, независимо от того, было ли выброшено исключение. Бонус: функция теперь явно сигнализирует, что принимает владение указателем.
Что такое Port? Удобная обёртка, инкапсулирующая ресурс:
class Port {
PortHandle port;
public:
Port(string_view destination) : port{open_port(destination)} { }
~Port() { close_port(port); }
operator PortHandle() { return port; }
// дескрипторы портов обычно нельзя клонировать — при необходимости запретите копирование и присваивание
Port(const Port&) = delete;
Port& operator=(const Port&) = delete;
};
Примечание
Если ресурс «неудобен» тем, что не представлен классом с деструктором, оберните его в класс или воспользуйтесь finally.
Смотрите также: RAII