Вообще говоря, любой класс, который управляет ресурсом, должен быть недоступен для копирования или иметь специальную семантику копирования. Обратное также верно: любой класс, который не подлежит копированию или нуждается в специальной семантике копирования, управляет ресурсом. «Управлять ресурсом» на языке C ++ на практике означает ответственность за некоторое пространство в памяти, или за подключение к сети или базе данных, или за дескриптор файла, или за отмену транзакции и т. Д.
Управление ресурсами содержит довольно много примеров. Это обязанности, которые выполняют префиксную операцию, суффиксную операцию и, возможно, некоторое промежуточное действие. Например, управление памятью включает в себя получение дескриптора адреса памяти, которым мы будем управлять, возможно, возиться с этой памятью, и, наконец, освободить дескриптор (потому что, если вы что-то любите, пусть это будет бесплатно).
template<typename T>
struct memory {
memory(T const& val = T()) : p(new T(val)) { }
~memory() { delete p }
T& operator*() const { return *p; }
private:
T* p;
};
// ...
{
memory<int> m0;
*m0 = 3;
std::cout << *m0 << '\n';
}
Этот класс memory
является почти правильным: он автоматически получает базовое пространство памяти и автоматически освобождает его, даже если исключение распространяется через некоторое время после того, как оно получило свой ресурс. Но рассмотрим этот сценарий:
{
memory<double> m1(3.14);
memory<double> m2(m1); // m2.p == m1.p (do you hear the bomb ticking?)
}
Поскольку мы не предоставили специализированную семантику копирования для memory
, компилятор предоставляет собственный конструктор копирования и назначение копирования. Они делают неправильную вещь: m2 = m1
означает m2.p = m1.p
, так что два указателя указывают на один и тот же адрес. Это неправильно, потому что когда m2
выходит из области видимости, он освобождает свой ресурс как хороший ответственный объект, а когда m1
затем выходит из области видимости, он тоже освобождает свой ресурс, тот же самый ресурс m2
уже освобожден, завершая двойной -delete - пресловутый сценарий неопределенного поведения. Более того, в C ++ чрезвычайно легко делать копии объекта, даже не замечая: функция принимает свой параметр по значению, возвращает свой параметр по значению или принимает свой параметр по ссылке, но затем вызывает другую функцию, которая сама принимает (или возвращает) свой параметр по значению ... Проще всего предположить, что вещи будут пытаться скопироваться.
Все это говорит о том, что когда класс 'raison d'être управляет ресурсом, вы сразу должны знать, что вам нужно обрабатывать копирование. Вы должны решить
- вы поддерживаете копирование, в то время как вы решаете, что означает копирование: безопасное совместное использование ресурса, выполнение глубокой копии основного ресурса, чтобы вообще не делиться, или объединение двух подходов, как в копирование при записи или ленивая копия. Какой бы путь вы ни выбрали, вам потребуется предоставить специальный конструктор копирования и оператор назначения копирования.
- или вы не поддерживаете какое-либо копирование ресурса, в этом случае вы отключаете конструктор копирования и оператор назначения копирования.
Я бы зашел так далеко и сказал, что управление ресурсами - это единственный случай, когда вы отключаете копирование или предоставляете специализированную семантику копирования. Это просто еще один взгляд на Правило Трех .