Вопрос. В каких случаях вызывается конструктор копирования?
Ответ:
1) При инициализации объекта оператором копирования: MyClass A = B;
2) При передаче в функцию копии параметра: MyClass A; f(A);
3) При создании временного объекта, возвращаемого функцией: MyClass f() { MyClass A; … return A; }
Пример 1. Вызов конструктора копирования при передаче параметра в функцию
class A {
int i;
public:
A(int n):
i(n)
{ cout << “Создание, i: “ << i << “\n”; }
~A() { cout << “Уничтожение, i: ” << i << “\n”;
void set_i(int n) { i = n; }
int get_i() { return i; }
};
void f(A ob) {
ob.set_i(2);
cout << “Это локальная переменная i: “ << ob.get_i() << “\n”;
}
int main() {
A o(1); // Создается объект “o”
f(o); // Объект «o» передается в качестве параметра в функцию f()
cout << “это переменная I в функции main: “ << o.get_i() << “\n”;
return 0;
}
В результате работы этой программы на экране появятся следующие строки:
Создание, i: 1
Это локальная переменная i: 2
Уничтожение, i: 2
Это переменная I в функции main: 1
Уничтожение, i: 1
В ходе работы этой программы конструктор вызывался лишь однажды, тогда как деструктор вызвался дважды.
Вопрос. Почему деструктор вызвался дважды?
Ответ. Когда объект передается в функцию, создается его копия, которая и становится параметром функции. При создании копии аргумента вызывается не обычный конструктор, а конструктор копирования. Если в классе нет явного определения конструктора копирования, то вызывается автоматический конструктор копирования, предусмотренный по умолчанию. Этот конструктор создает побитовую копию объекта. При завершении работы функции f копия объекта выходит из зоны видимости, поэтому вызывается его деструктор. При завершении работы функции main деструктор вызывается для уничтожения объекта o.
Вопрос. Почему при создании копии объекта не вызывается обычный конструктор?
Ответ. Так как обычный конструктор изменил бы состояния копируемого объекта на первоначальное.
Вопрос. Какие проблемы могут возникнуть из-за двойного вызова деструктора?
Ответ. Например, если в обычном конструкторе выделяется динамическая память, а в деструкторе она освобождается, то локальная копия будет освобождать эту выделенную память уже при завершении работы функии f, что приведет к повреждению оригинала!
Вопрос. Что делать, чтобы избежать подобных проблем?
Ответ. В классе необходимо явно определить конструктор копирования.
Конструктор копирования выглядит следующим образом:
<имя класса> (const <имя_класса> &<ссылка на объект>) {
<тело конструктора>
}
Ссылка на объект связана с объектом, стоящим в правой части инициализации.
Пример 2. Определение конструктора копирования
class array {
int *p; // Указатель на выделяемую память, в которой хранится содержимое массива
int size; // Размер массива
public:
array (int sz): size(sz) {
p = new int[sz];
}
array(const array &a) : size(a.size) {
int i;
p = new int[a.size];
for (i = 0; i < a.size; i++) p[i] = a.p[i];
}
~array() { delete [] p; }
void put(int i, int val) {
if ((i >= 0) && (i < size)) p[i] = val;
}
int get(int i) {
return p[i];
}
};
int main() {
array ar(10);
int i;
for (i = 0; i < 10; i++) ar.put(i, i);
array x(ar); // Вызов конструктора копирования
}
Вопрос. Будет ли вызываться конструктор копирования в следующем коде:
array a(10);
array b(10);
b = a;
Ответ. Нет, в данном случае оператор «=» выполняет присваивание одного объекта другому.
Если оператор присваивания не перегружен, то создается побитовая копия объекта, расположенного в его правой части. В этом случае опять возникает проблема двойного вызова деструктора для памяти, захваченной динамически.
Следовательно, необходимо также переопределить оператор присваивания:
Пример 3. Перегрузка оператора присваивания в классе array
class array {
…
array operator=(array op2) {
int i;
size = op2.size;
p = new int[size];
for (i = 0; i < size; i++) p[i] = op2.get(i);
}
};
Задача. Найдите ошибку в примере 3.
Подсказка. Ошибка связана с использованием private элементов класса op2.