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

  // ...

  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

118.3.2. Отладка конструкторов и деструкторов

 

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

• Когда создается объект класса

X
, вызывается один из его конструкторов.

• Когда уничтожается объект типа

X
, вызывается его деструктор.

Деструктор вызывается всегда, когда уничтожается объект класса; это происходит, когда объект выходит из области видимости, программа прекращает работу или к указателю на объект применяется оператор

delete
. Подходящий конструктор вызывается каждый раз, когда создается объект класса; это происходит при инициализации переменной, при создании объекта с помощью оператора
new
(за исключением встроенных типов), а также при копировании объекта.

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

struct X { // простой тестовый класс

  int val;

  void out(const string& s)

    { cerr << this << "–>" << s << ": " << val << "\n"; }

  X(){ out("X()"); val=0; }      // конструктор по умолчанию

  X(int v) { out( "X(int)"); val=v; }

  X(const X& x){ out("X(X&) "); val=x.val; } // копирующий

                                             // конструктор

  X& operator=(const X& a)       // копирующее присваивание

    { out("X::operator=()"); val=a.val; return *this; }

  ~X() { out("~X()"); }          // деструктор

};

Проследим, что происходит при выполнении операций над объектом класса

X
. Рассмотрим пример.

X glob(2);   // глобальная переменная

X copy(X a) { return a; }

X copy2(X a) { X aa = a; return aa; }

X& ref_to(X& a) { return a; }

X* make(int i) { X a(i); return new X(a); }

struct XX { X a; X b; };

int main()

{

  X loc(4);         // локальная переменная

  X loc2 = loc;

  loc = X(5);

  loc2 = copy(loc);

  loc2 = copy2(loc);

  X loc3(6);

  X& r = ref_to(loc);

  delete make(7);

  delete make(8);

  vector<X> v(4);

  XX loc4;

  X* p = new X(9);  // объект класса Х в свободной памяти

  delete p;

  X* pp = new X[5]; // массив объектов класса X

                    // в свободной памяти

  delete[]pp;

}

Попробуйте выполнить эту программу.

ПОПРОБУЙТЕ

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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 В зависимости от качества вашего компилятора вы можете заметить пропущенные копии, связанные с вызовами функций
copy()
и
copy2()
. Мы (люди) видим, что эти функции ничего не делают; они просто копируют значение из потока ввода в поток вывода без каких-либо изменений. Если компилятор настолько хорош, что заметит это, то сможет удалить эти вызовы конструктора копирования. Иначе говоря, компилятор может предполагать, что конструктор копирования только копирует и ничего больше не делает. Некоторые компиляторы настолько “умны”, что могут исключить фиктивные копии.

Так зачем же возиться с этим “глупым классом

X
”? Это напоминает упражнения для пальцев, которые выполняют музыканты. После этих упражнений многие вещи, которые обладают намного большим смыслом, становятся понятнее и легче. Кроме того, если у вас возникнут проблемы с конструкторами и деструкторами, рекомендуем вставить в них операторы вывода и посмотреть, как они работают. Для более крупных программ такая отладка становится утомительной, но для них изобретены аналогичные технологии отладки. Например, мы можем выявить, происходит ли утечка памяти, определив, равна ли нулю разность между количеством вызовов конструктора и деструктора. Программисты часто забывают определить копирующие конструкторы и копирующее присваивание для классов, выделяющих память или содержащих указатели на объекты. Это порождает проблемы (которые, впрочем, легко устранить).

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