#include <iostream>
using namespace std;
class OBJ {
int i;
public:
void set_i(int x) { i = x; }
void out_i() { cout << i << " "; }
};
void f(OBJ x)
{
x.out_i(); // Выводит число.
х.set_i(100); // Устанавливает только локальную копию.
x.out_i(); // Выводит число 100.
}
int main()
{
OBJ о;
о.set_i(10);
f(о);
o.out_i(); // По-прежнему выводит число 10, значение переменной i не изменилось.
return 0;
}
Вот как выглядят результаты выполнения этой программы.
10 100 10
Как подтверждают эти результаты, модификация объекта x в функции f() не влияет на объект o в функции main().
Несмотря на то что передача функциям несложных объектов в качестве аргументов — довольно простая процедура, при этом могут происходить непредвиденные события, имеющие отношение к конструкторам и деструкторам. Чтобы разобраться в этом, рассмотрим следующую программу.
// Конструкторы, деструкторы и передача объектов.
#include <iostream>
using namespace std;
class myclass {
int val;
public:
myclass(int i) { val = i; cout << "Создание\n"; }
~myclass() { cout << "Разрушение\n"; }
int getval() { return val; }
};
void display(myclass ob)
{
cout << ob.getval() << '\n';
}
int main()
{
myclass a(10);
display(a);
return 0;
}
При выполнении эта программа выводит следующие неожиданные результаты.
Создание
10
Разрушение
Разрушение
Как видите, здесь выполняется одно обращение к функции конструктора (при создании объекта a), но почему-то два обращения к функции деструктора. Давайте разбираться, в чем тут дело.
При передаче объекта функции создается его копия (и эта копия становится параметром в функции). Создание копии означает "рождение" нового объекта. Когда выполнение функции завершается, копия аргумента (т.е. параметр) разрушается. Здесь возникает сразу два вопроса. Во-первых, вызывается ли конструктор объекта при создании копии? Во-вторых, вызывается ли деструктор объекта при разрушении копии? Ответы могут удивить вас.
Когда при вызове функции создается копия аргумента, обычный конструктор не вызывается. Вместо этого вызывается конструктор копии объекта. Конструктор копии определяет, как должна быть создана копия объекта. (Как создать конструктор копии, будет показано ниже в этой главе.) Но если в классе явно не определен конструктор копии, C++ предоставляет его по умолчанию. Конструктор копии по умолчанию создает побитовую (т.е. идентичную) копию объекта. Поскольку обычный конструктор используется для инициализации некоторых аспектов объекта, он не должен вызываться для создания копии уже существующего объекта. Такой вызов изменил бы его содержимое. При передаче объекта функции имеет смысл использовать текущее состояние объекта, а не его начальное состояние.
Но когда функция завершается и разрушается копия объекта, используемая в качестве аргумента, вызывается деструктор этого объекта. Необходимость вызова деструктора связана с выходом объекта из области видимости. Именно поэтому предыдущая программа имела два обращения к деструктору. Первое произошло при выходе из области видимости параметра функции display(), а второе— при разрушении объекта a в функции main() по завершении программы.