Читаем Программирование онлайн бесплатно
Если класс обладает ресурсами, то он должен иметь деструктор. Ресурс — это то, что вы “где-то взяли” и должны вернуть, когда закончите его использовать. Очевидным примером является память, выделенная с помощью оператора new, которую вы должны освободить, используя оператор
delete или delete[]. Для хранения своих элементов наш класс vector требует память, поэтому он должен ее вернуть; следовательно, он должен иметь деструктор. Другие ресурсы, которые используются в более сложных программах, — это файлы (если вы открыли файл, то должны его закрыть),
Другой признак того, что в классе необходим деструктор, — это наличие членов класса, которые являются указателями или ссылками. Если одним из членов класса является указатель или ссылка, скорее всего, в нем требуются деструктор и операции копирования.
Класс, который должен иметь деструктор, практически всегда требует наличия копирующего конструктора и копирующего присваивания. Причина состоит в том, что если объект обладает ресурсом (и имеет указатель — член класса, ссылающийся на это ресурс), то копирование по умолчанию (почленное поверхностное копирование) почти наверняка приведет к ошибке. Классическим примером является класс
vector.
Если производный класс должен иметь деструктор, то базовый класс должен иметь виртуальный деструктор (см. раздел 17.5.2).
Конструктор, имеющий один аргумент, определяет преобразование типа этого аргумента в свой класс. Это может оказаться очень полезным. Рассмотрим пример.
class complex {
public:
complex(double); // определяет преобразование double в complex
complex(double,double);
// ...
};
complex z1 = 3.18; // OK: преобразует 3.18 в (3.18,0)
complex z2 = complex(1.2, 3.4);
Однако неявные преобразования следует применять скупо и осторожно, поскольку они могут вызвать неожиданные и нежелательные эффекты. Например, наш класс
vector, определенный выше, имеет конструктор, принимающий аргумент типа int. Отсюда следует, что он определяет преобразование типа int в класс vector. Рассмотрим пример.
class vector {
// ...
vector(int);
// ...
};
vector v = 10; // создаем вектор из 10 элементов типа double
v = 20; // присваиваем вектору v новый вектор
// из 20 элементов типа double to v
void f(const vector&);
f(10); // Вызываем функцию f с новым вектором,
// состоящим из 10 элементов типа double
Кажется, мы получили больше, чем хотели. К счастью, подавить такое неявное преобразование довольно просто. Конструктор с ключевым словом
explicit допускает только обычную семантику конструирования и не допускает неявные преобразования. Рассмотрим пример.
class vector {
// ...
explicit vector(int);
// ...
};
vector v = 10; // ошибка: преобразования int в vector нет
v = 20; // ошибка: преобразования int в vector нет
vector v0(10); // OK
void f(const vector&);
f(10); // ошибка: преобразования int в vector нет
f(vector(10)); // OK
Для того чтобы избежать неожиданных преобразований, мы — и стандарт языка — потребовали, чтобы конструктор класса vector с одним аргументом имел спецификатор explicit. Очень жаль, что все конструкторы не имеют спецификатора explicit по умолчанию; если сомневаетесь, объявляйте конструктор, который может быть вызван с одним аргументом, используя ключевое слово explicit.
Конструкторы и деструкторы вызываются в точно определенных и предсказуемых местах программы. Однако мы не всегда пишем явные вызовы, например
vector(2); иногда мы пишем объявление объекта класса vector, передаем его как аргумент функции по значению или создаем в свободной памяти с помощью оператора new. Это может вызвать замешательство у людей, думающих в терминах синтаксиса. Не существует синтаксической конструкции, которая осуществляла бы диспетчеризацию вызовов конструкторов. О конструкторах и деструкторах проще думать следующим образом.



