Литмир - Электронная Библиотека
A
A

Конструкторы, имеющие аргументы, сильно зависят от класса, в котором они реализованы. Остальные операции имеют более или менее стандартную структуру.

Как понять, что в классе необходим конструктор по умолчанию? Он требуется тогда, когда мы хотим создавать объекты класса без указания инициализатора. Наиболее распространенный пример такой ситуации возникает, когда мы хотим поместить объекты класса в стандартный контейнер, имеющий тип

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
.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Если класс обладает ресурсами, то он должен иметь деструктор. Ресурс — это то, что вы “где-то взяли” и должны вернуть, когда закончите его использовать. Очевидным примером является память, выделенная с помощью оператора new, которую вы должны освободить, используя оператор
delete
или
delete[]
. Для хранения своих элементов наш класс vector требует память, поэтому он должен ее вернуть; следовательно, он должен иметь деструктор. Другие ресурсы, которые используются в более сложных программах, — это файлы (если вы открыли файл, то должны его закрыть), блокировки (locks), дескрипторы потоков (thread handles) и двунаправленные каналы (sockets), используемые для обеспечения взаимосвязи между процессами и удаленными компьютерами.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Другой признак того, что в классе необходим деструктор, — это наличие членов класса, которые являются указателями или ссылками. Если одним из членов класса является указатель или ссылка, скорее всего, в нем требуются деструктор и операции копирования.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Класс, который должен иметь деструктор, практически всегда требует наличия копирующего конструктора и копирующего присваивания. Причина состоит в том, что если объект обладает ресурсом (и имеет указатель — член класса, ссылающийся на это ресурс), то копирование по умолчанию (почленное поверхностное копирование) почти наверняка приведет к ошибке. Классическим примером является класс
vector
.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Если производный класс должен иметь деструктор, то базовый класс должен иметь виртуальный деструктор (см. раздел 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);

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Однако неявные преобразования следует применять скупо и осторожно, поскольку они могут вызвать неожиданные и нежелательные эффекты. Например, наш класс
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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Кажется, мы получили больше, чем хотели. К счастью, подавить такое неявное преобразование довольно просто. Конструктор с ключевым словом
explicit
допускает только обычную семантику конструирования и не допускает неявные преобразования. Рассмотрим пример.

class vector {

242
{"b":"847443","o":1}