Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
13.1.3. Деструктор
Действие деструктора противоположно действию конструктора: конструкторы инициализируют нестатические переменные-члены объекта, а также могут выполнять другие действия; деструкторы осуществляют все действия, необходимые для освобождения использованных объектом ресурсов и удаления нестатических переменных-членов объекта.
Деструктор — это функция-член с именем класса, предваряемым тильдой (~). У нее нет ни параметров, ни возвращаемого значения:
class Foo {
public:
~Foo(); // деструктор
// ...
};
Поскольку деструктор не получает никаких параметров, он не может быть перегружен. Для каждого класса возможен только один деструктор.
Что делает деструкторПодобно тому, как конструктор имеет часть инициализации и тело (см. раздел 7.5.1), деструктор имеет тело и часть удаления. В конструкторе переменные-члены инициализируются перед выполнением тела, а инициализация членов осуществляется в порядке их объявления в классе. В деструкторе сначала выполняется тело, а затем происходит удаление членов. Переменные- члены удаляются в порядке, обратном их инициализации.
Тело деструктора осуществляет все операции, которые разработчик класса считает необходимыми выполнить после использования объекта. Как правило, деструктор освобождает ресурсы объекта, зарезервированные на протяжении его существования.
У деструктора нет ничего похожего на список инициализации конструктора для контроля удаления переменных-членов; часть удаления неявна. Происходящее при удалении переменной-члена зависит от его типа. Члены типа класса удаляются за счет выполнения его собственного деструктора. У встроенных типов нет деструкторов, поэтому для удаления членов встроенного типа не делается ничего.
Неявное удаление члена-указателя встроенного типа не удаляет объект, на который он указывает.
В отличие от обычных указателей, интеллектуальные указатели (см. раздел 12.1.1) являются классами и имеют деструкторы. Поэтому, в отличие от обычных указателей, члены, являющиеся интеллектуальными указателями, автоматически удаляются на фазе удаления.
Когда происходит вызов деструктораДеструктор автоматически используется всякий раз, когда удаляется объект его типа.
• Переменные удаляются, когда выходят из области видимости.
• Переменные-члены объекта удаляются при удалении объекта, которому они принадлежат.
• Элементы в контейнере (будь то библиотечный контейнер или массив) удаляются при удалении контейнера.
• Динамически созданные объекты удаляются при применении оператора delete к указателю на объект (см. раздел 12.1.2).
• Временные объекты удаляются в конце выражения, в котором они были созданы.
Поскольку деструкторы выполняются автоматически, программы могут резервировать ресурсы и (обычно) не заботиться о том, когда они освобождаются.
Например, следующий фрагмент кода определяет четыре объекта класса Sales_data:
{ // новая область видимости
// p и p2 указывают на динамически созданные объекты Sales_data
*p = new Sales_data; // p - встроенный указатель
auto p2 = make_shared<Sales_data>(); // p2 - shared_ptr
Sales_data item(*p); // конструктор копий копирует *p в item
vector<Sales_data> vec; // локальный объект
vec.push_back(*p2); // копирует объект, на который указывает p2
delete p; // деструктор вызывается для объекта, на
// который указывает p
} // выход из локальной области видимости; деструктор вызывается
// для item, p2 и vec
// удаление p2 уменьшает его счетчик пользователей; если значение
// счетчика дойдет до 0, объект освобождается
// удаление вектора vec удалит и его элементы
Каждый из этих объектов содержит член типа string, который резервирует динамическую память для содержания символов переменной-члена bookNo. Но единственная память, которой код должен управлять непосредственно, — это самостоятельно зарезервированный объект. Код непосредственно освобождает только динамически созданный объект, связанный с указателем p.
Другие объекты класса Sales_data автоматически удаляются при выходе из области видимости. По завершении блока vec, p2 и item выходят из области видимости, это означает вызов деструкторов классов vector, shared_ptr и Sales_data для соответствующих объектов. Деструктор класса vector удалит элемент, помещенный в вектор vec. Деструктор класса shared_ptr осуществит декремент счетчика ссылок объекта, на который указывает указатель p2. В данном примере этот счетчик достигнет нуля, поэтому деструктор класса shared_ptr удалит объект класса Sales_data, зарезервированный с использованием указателя p2.
Во всех случаях деструктор класса Sales_data неявно удаляет переменную-член bookNo. Удаление переменной-члена bookNo запускает деструктор класса string, который освобождает память, используемую для хранения ISBN.
Когда из области видимости выходит ссылка или указатель на объект, деструктор не выполняется.
Синтезируемый деструкторКомпилятор определяет синтезируемый деструктор (synthesized destructor) для любого класса, который не определяет собственный деструктор. Подобно конструкторам копий и операторам присвоения копии, определение для некоторых классов синтезируемого деструктора предотвращает удаление объектов этого типа (раздел 13.1.6). В противном случае у синтезируемого деструктора будет пустое тело.
Например, синтезируемый деструктор класса Sales_data эквивалентен следующему:
class Sales_data {
public:
// не делать ничего, кроме удаления переменных-членов,
// осуществляемого автоматически
~Sales_data() { }
// другие члены как прежде
};
Переменные-члены автоматически удаляются после выполнения (пустого) тела деструктора. В частности, деструктор класса string будет выполнен для освобождения памяти, используемой переменной-членом bookNo.
Важно понять, что само тело деструктора не удаляет переменные-члены непосредственно. Они удаляются в ходе неявной фазы удаления, которая следует за телом деструктора. Тело деструктора выполняется в дополнение к удалению членов, осуществляемому в ходе удаления объекта.
Упражнения раздела 13.1.3Упражнение 13.9. Что такое деструктор? Что делает синтезируемый деструктор? Когда деструктор синтезируется?
Упражнение 13.10. Что произойдет при удалении объекта класса StrBlob? А класса StrBlobPtr?
Упражнение 13.11. Добавьте деструктор в класс HasPtr из предыдущих упражнений.
Упражнение 13.12. Сколько вызовов деструктора происходит в следующем фрагменте кода?
bool fcn(const Sales_data *trans, Sales_data accum) {
Sales_data item1(*trans), item2(accum);
return item1.isbn() != item2.isbn();
}
Упражнение 13.13. Наилучший способ изучения функций-членов управления копированием и конструкторов — это определить простой класс с этими функциями-членами, каждая из которых выводит свое имя:
struct X {
X() {std::cout << "X()" << std::endl;}
X(const X&) {std::cout << "X(const X&)" << std::endl;}
};
Добавьте в структуру X оператор присвоения копии и деструктор, а затем напишите программу, использующую объекты класса X различными способами: передайте их как ссылочный и не ссылочный параметры; динамически зарезервируйте их; поместите в контейнеры и т.д. Изучайте вывод, пока не начнете хорошо понимать, когда и почему используется каждая функция-член управления копированием. По мере чтения вывода помните, что компилятор может обойти вызовы конструктора копий.
13.1.4. Правило три/пять
Как уже упоминалось, существуют три базовых функции, контролирующих копирование объектов класса: конструктор копий, оператор присвоения копии и деструктор. Кроме того, как будет продемонстрировано в разделе 13.6, по новому стандарту класс может также определить конструктор перемещения и оператор присваивания при перемещении.
Определять все эти функции не обязательно: вполне можно определить один или два из них, не определяя все. Эти функции можно считать модулями. Если нужен один, не обязательно определять их все.