Хотя "скрытые" вызовы конструкторов копии могут отдавать мистикой, нельзя отрицать тот факт, что практически каждый класс в профессионально написанных программах содержит явно определенный конструктор копии, без которого не избежать побочных эффектов, возникающих в результате создания по умолчанию побитовых копий объекта.
Как уже неоднократно упоминалось в этой книге, C++ — очень мощный язык. Он имеет множество средств, которые наделяют его широкими возможностями, но при этом его можно назвать сложным языком. Конструкторы копии представляют собой механизм, на который ссылаются многие программисты как на основной пример сложности языка, поскольку это средство не воспринимается на интуитивном уровне. Начинающие программисты часто не понимают, почему так важен конструктор копии. Для многих не сразу становится очевидным ответ на вопрос: когда нужен конструктор копии, а когда — нет. Эта ситуация часто выражается в такой форме: "А не существует ли более простого способа?". Ответ также непрост: и да, и нет!
Такие языки, как Java и С#, не имеют конструкторов копии, поскольку ни в одном из них не создаются побитовые копии объектов. Дело в том, что как Java, так и C# динамически выделяют память для всех объектов, а программист оперирует этими объектами исключительно через ссылки. Поэтому при передаче объектов в качестве параметров функции или при возврате их из функций в копиях объектов нет никакой необходимости.
Тот факт, что ни Java, ни C# не нуждаются в конструкторах копии, делает эти языки проще, но за простоту тоже нужно платить. Работа с объектами исключительно посредством ссылок (а не напрямую, как в C++) налагает ограничения на тип операций, которые может выполнять программист. Более того, такое использование объектных ссылок в Java и C# не позволяет точно определить, когда объект будет разрушен. В C++ же объект всегда разрушается при выходе из области видимости.
Язык C++ предоставляет программисту полный контроль над ситуациями, складывающимися в программе, поэтому он несколько сложнее, чем Java и С#. Это — цена, которую мы платим за мощность программирования.
Ключевое слово this — это указатель на объект, который вызывает функцию-член.
При каждом вызове функции-члена ей автоматически передается указатель, именуемый ключевым словом this, на объект, для которого вызывается эта функция. Указатель this — это неявный параметр, принимаемый всеми функциями-членами. Следовательно, в любой функции-члене указатель this можно использовать для ссылки на вызывающий объект.
Как вы знаете, функция-член может иметь прямой доступ к закрытым (private) членам данных своего класса.
Например, у нас определен такой класс.
class cl {
int i;
void f() { ... };
// . . .
};
В функции f() можно использовать следующую инструкцию для присваивания члену i значения 10.
i = 10;
В действительности предыдущая инструкция представляет собой сокращенную форму следующей.
this->i = 10;
Чтобы понять, как работает указатель this, рассмотрим следующую короткую программу.
#include <iostream>
using namespace std;
class cl {
int i;
public:
void load_i(int val) { this->i = val; } // то же самое, что i = val
int get_i() { return this->i; } // то же самое, что return i
};
int main()
{
cl o;
o.load_i (100);
cout << о.get_i();
return 0;
}
При выполнений эта программа отображает число 100.
Безусловно, предыдущий пример тривиален, но в нем показано, как можно использовать указатель this. Скоро вы поймете, почему указатель this так важен для программирования на C++.
Важно! Функции-"друзья" не имеют указателя this, поскольку они не являются членами класса. Только функции-члены имеют указатель this.