LEC_cpp2 (1119517), страница 2
Текст из файла (страница 2)
Реализация оператора присваивания обычно содержит следующую последовательность действий:
-
защита от присваивания самому себе ( if (this != &a) ...),
-
очистка старого объекта,
-
копирующая инициализация старого объекта.
Рассмотрим пример, когда нам необходимо определить свои конструктор копирования, оператор присваивания и деструктор.
class string {
char * p; // здесь потребуется динамическая память,
// т.е. имеем неплоский класс
int size;
public:
string (const char *str);
//const, чтобы передавать параметры-константы и чтобы обозначить, что //параметр конструктора внутри конструктора изменяться не будет.
string (string & a);
~string () {delete [] p;}
string& operator= (string & a);
}
string::string (const char *str) {
p = new char [(size = strlen (str))+1];
strcpy (p, str);
};
Если бы не были определены свои конструктор копирования и оператор присваивания, посмотрим, что бы произошло при работе такой функции f.
5
A l i c e \0





string s1 (“Alice”); s1
5
string s2 = s1; s2
K a t e \0








4

s3
...
s3 = s1;
}
Кроме того, деструктор будет работать 3 раза (при выходе их тела f). Правда, это не смертельно, т.к. delete от NULL допустимо
А если структура программы такова?
{... s1...s2 {...s3...}...s1...s2}
При уничтожении s3 подвиснут s1 и s2.
Чтобы избежать подобных конфликтов, определим свои конструктор копирования и оператор присваивания.
string::string (string & a) {
p = new char [(size = a.size)+1];
strcpy (p, a.p);
}
string& string::operator = (string & a) {
if (this == &a) return *this; // если a = a
delete [] p;
p = new char [(size = a.size)+1];
strcpy (p, a.p);
return *this;
}
Для нашего примера s3 =s1 s3 - это *this, а s1- a.
Композиция объектов (или строгая агрегация).
Объекты могут содержать внутри себя объекты других классов, такое отношение называется композиция.
class Point { class Z{
int x; Point p;
int y; int z;
public: public:
Point(); Z(int);
Point(int, int); };
};
Объекты класса Z и класса Point должны быть созданы одновременно, и уничтожены одновременно.
Z* z = new Z(1); // Point(); int(); Z(1);
При вызове конструктора класса:
-
вызываются конструкторы базовых классов (если есть наследование class Z : public Y {…};),
-
автоматически вызываются конструкторы умолчания всех вложенных объектов в порядке их описания в классе,
-
вызывается собственный конструктор, при его вызове все поля класса уже проинициализированы, следовательно, их можно использовать.
delete z; // ~Z(); ~int(); ~Point();
Деструкторы вызываются в обратном порядке:
-
собственный деструктор, при этом поля класса ещё не очищены, следовательно, доступны для использования,
-
автоматически вызываются деструкторы для всех вложенных объектов в порядке, обратном порядку их описания в классе,
-
деструкторы базовых классов (если есть наследование).
Рассмотрим теперь инициализацию вложенных объектов. Единственный способ инициализировать вложенный объект до вызова собственного конструктора – это использовать список инициализации при описании конструктора.
Z::Z( int c ) : p(1, 2) { z = c; }
Запись после двоеточия аналогична определению объекта p: Point p (1, 2) ; но в определении класса мы так написать не можем.
В списке инициализации можно указывать несколько элементов, например, можно инициализировать и поля базовых типов данных:
Z::Z( int c ) : p(1, 2), z(c) {}
Если в конструкторе класса задан список инициализации, то будут вызваны конструкторы из списка с заданными параметрами. Если для какого-то подобъекта инициализация в списке не указана, то будет вызван конструктор умолчания.
Итак, обычный конструктор класса вызывается:
-
при создании объекта (при обработке описания объекта),
-
при создании объекта в динамической памяти (по new), при этом сначала в «куче» отводится необходимая память, а затем работает соответствующий конструктор,
-
при композиции объектов наряду с собственным конструктором вызывается конструктор объекта – члена класса,
-
при создании объекта производного класса также вызывается конструктор и базового класса.
Конструктор копирования вызывается при инициализации объектов при создании:
-
в случае: Box a (1, 2, 3);
Box b = a; // a – параметр конструктора копирования,
-
в случае: Box c = Box (3, 4, 5); // сначала создается временный объект и вызывается обычный конструктор, а затем работает конструктор копирования при создании объекта с; если компилятор оптимизирующий, вызывается только обычный конструктор с указанными параметрами;
-
при передаче параметров функции по значению (при создании локального объекта);
-
при возвращении результата работы функции в виде объекта.
Деструктор вызывается:
-
при свертке стека - при выходе из блока описания объекта, в частности при обработке исключений, завершении работы функции;
-
при уничтожении временных объектов - сразу, как только завершается конструкция, в которой они использовались;
-
при выполнении операции delete для указателя на объект (инициализация указателя - с помощью операции new), при этом сначала работает деструктор, а затем освобождается память.
-
при завершении работы программы при удалении глобальных/статических объектов.
Конструкторы вызываются в порядке определения объектов в блоке. При выходе из блока для всех автоматических объектов вызываются деструкторы, в порядке, противоположном порядку выполнения конструкторов.
8