Конструкторы, имеющие аргументы, сильно зависят от класса, в котором они реализованы. Остальные операции имеют более или менее стандартную структуру.
Как понять, что в классе необходим конструктор по умолчанию? Он требуется тогда, когда мы хотим создавать объекты класса без указания инициализатора. Наиболее распространенный пример такой ситуации возникает, когда мы хотим поместить объекты класса в стандартный контейнер, имеющий тип
vector
. Приведенные ниже инструкции работают только потому, что для типов
int
,
string
и
vector<int>
существуют значения, предусмотренные по умолчанию.
vector<double> vi(10); // вектор из 10 элементов типа double,
// каждый из них инициализирован 0.0
vector<string> vs(10); // вектор из 10 элементов типа string,
// каждый из них инициализирован ""
vector<vector< int> > vvi(10); // вектор из 10 векторов,
// каждый из них
// инициализирован конструктором
vector()
Итак, иметь конструктор по умолчанию часто бывает полезно. Возникает следующий вопрос: а когда именно целесообразно иметь конструктор по умолчанию? Ответ: когда мы можем установить инвариант класса с осмысленным и очевидным значением по умолчанию. Для числовых типов, таких как
int
и
double
, очевидным значением является
0
(для типа
double
оно принимает вид
0.0
). Для типа
string
очевидным выбором является
""
. Для класса
vector
можно использовать пустой вектор. Если тип
T
имеет значение по умолчанию, то оно задается конструктором
T()
. Например,
double()
равно
0.0
,
string()
равно
""
, а
vector<int>()
— это пустой
vector
, предназначенный для хранения переменных типа
int
.
Если класс обладает ресурсами, то он должен иметь деструктор. Ресурс — это то, что вы “где-то взяли” и должны вернуть, когда закончите его использовать. Очевидным примером является память, выделенная с помощью оператора new, которую вы должны освободить, используя оператор
delete
или
delete[]
. Для хранения своих элементов наш класс vector требует память, поэтому он должен ее вернуть; следовательно, он должен иметь деструктор. Другие ресурсы, которые используются в более сложных программах, — это файлы (если вы открыли файл, то должны его закрыть),
блокировки (locks),
дескрипторы потоков (thread handles) и
двунаправленные каналы (sockets), используемые для обеспечения взаимосвязи между процессами и удаленными компьютерами.
Другой признак того, что в классе необходим деструктор, — это наличие членов класса, которые являются указателями или ссылками. Если одним из членов класса является указатель или ссылка, скорее всего, в нем требуются деструктор и операции копирования.
Класс, который должен иметь деструктор, практически всегда требует наличия копирующего конструктора и копирующего присваивания. Причина состоит в том, что если объект обладает ресурсом (и имеет указатель — член класса, ссылающийся на это ресурс), то копирование по умолчанию (почленное поверхностное копирование) почти наверняка приведет к ошибке. Классическим примером является класс
vector
.
Если производный класс должен иметь деструктор, то базовый класс должен иметь виртуальный деструктор (см. раздел 17.5.2).
18.3.1. Явные конструкторы
Конструктор, имеющий один аргумент, определяет преобразование типа этого аргумента в свой класс. Это может оказаться очень полезным. Рассмотрим пример.
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 {