Перейти к содержимому
Теория · Классы и ООП · урок 4 из 13

Деструктор, правило нуля и const-методы

Конструктор выполняется, когда объект создаётся. У него есть пара — метод, который выполняется, когда объект перестаёт существовать. Порядок этих вызовов видно, если оба печатают имя объекта:

C++
#include <iostream>

struct Tracer {
    char name;
    Tracer(char n) : name(n) { std::cout << '+' << name << ' '; }
    ~Tracer() { std::cout << '-' << name << ' '; }
};

int main() {
    Tracer a('a');
    {
        Tracer b('b');
    }
    std::cout << "end ";
}  // +a +b -b end -a

Деструктор

Деструктор (destructor) — метод с именем класса и тильдой впереди, например ~Tracer, без параметров и без типа результата. Он выполняется сам, когда заканчивается время жизни объекта. Для локальной переменной это выход из блока, где она объявлена: b уничтожается на закрывающей скобке внутреннего блока, а a — в конце main. Объекты одного блока уничтожаются в обратном порядке: созданный последним — первым.

Деструктор нужен, когда объект сам владеет ресурсом, который надо вернуть системе, например выделенной памятью. Ресурс получают в конструкторе, а возвращают в деструкторе, и забыть о возврате уже нельзя.

Правило нуля

Большинству классов свой деструктор не нужен. Строки std::string и векторы std::vector сами освобождают свою память, а деструкторы полей вызываются автоматически, когда уничтожается объект, которому они принадлежат:

C++
#include <string>
#include <vector>

struct Contact {
    std::string name;
    std::vector<std::string> phones;
};

Деструктор в Contact не объявлен, но при уничтожении контакта компилятор вызывает деструкторы name и phones. Копирование тоже работает: копия контакта получает собственные строку и вектор. Это правило нуля (rule of zero): класс, поля которого сами управляют ресурсами, не объявляет ни деструктора, ни своих методов копирования.

const-методы

Объект, объявленный с const, менять нельзя. Можно ли вызвать метод у такого объекта, компилятор решает по объявлению метода, а не по его телу. Поэтому у константного объекта, как и через ссылку (reference) const T&, можно вызвать только const-методы (const member functions). Это методы со словом const после списка параметров:

C++
class Temperature {
public:
    Temperature(int celsius) : celsius_(celsius) {}
    int celsius() const { return celsius_; }
    void warm() { ++celsius_; }

private:
    int celsius_;
};

Для const Temperature t(20); вызов t.celsius() компилируется, а t.warm() — нет. Внутри const-метода объект тоже константный: присвоить значение полю там нельзя, если поле не объявлено со словом mutable. Поэтому метод, который только читает поля, объявляют const: его можно вызвать для любого объекта, и константного, и обычного.

Частая ошибка

Метод только читает поле, но объявлен без const:

C++
class Temperature {
public:
    Temperature(int celsius) : celsius_(celsius) {}
    int celsius() { return celsius_; }

private:
    int celsius_;
};

int twice(const Temperature& t) {
    return t.celsius() * 2;  // ✗
}
TEXT
error: 'this' argument to member function 'celsius' has type 'const Temperature', but function is not marked const

Параметр t — константная ссылка, а celsius() не обещает оставить объект неизменным, поэтому вызов не компилируется. Добавьте const после списка параметров: int celsius() const.