Лекции.ИНФО


Конструктор копии применяется только к инициализациям. Он не применяется к присваиваниям.



Узелок на память. Конструкторы копии не оказывают никакого влияния на операции присваивания.

Конструктор копии вызывается в случае, когда один объект инициализирует другой.

Вот как выглядит самый распространенный формат конструктора копии.

имя_класса (const имя_класса &obj) {

 // тело конструктора

}

Здесь элемент obj означает ссылку на объект, которая используется для инициализации другого объекта. Например, предположим, у нас есть класс myclass и объект y типа myclass, тогда при выполнении следующих инструкций будет вызван конструктор копии класса myclass.

myclass х = у; // Объект у явно инициализирует объект x

Х.func1(у); // Объект у передается в качестве аргумента.

у = func2(); // Объект у принимает объект, возвращаемый функцией.

В первых двух случаях конструктору копии будет передана ссылка на объект у, а в третьем — ссылка на объект, возвращаемый функцией func2().

Чтобы глубже понять назначение конструкторов копии, рассмотрим подробнее их роль в каждой из этих трех ситуаций.

Конструкторы копии и параметры функции

При передаче объекта функции в качестве аргумента создается копия этого объекта. Если в классе определен конструктор копии, то именно он и вызывается для создания копии. Рассмотрим программу, в которой используется конструктор копии для надлежащей обработки объектов типа myclass при их передаче функции в качестве аргументов. (Ниже приводится корректная версия некорректной программы, представленной выше в этой главе.)

// Использование конструктора копии для

// определения параметра.

#include <iostream>

#include <cstdlib>

using namespace std;

class myclass {

  int *p;

 public:

  myclass(int i); // обычный конструктор

  myclass(const myclass &ob); // конструктор копии

  ~myclass();

  int getval() { return *p; }

};

// Конструктор копии.

myclass::myclass(const myclass &obj)

{

 p = new int;

 *p = *obj.p; // значение копии

 cout << "Вызван конструктор копии.";

}

// Обычный конструктор.

Myclass::myclass(int i)

{

 cout << "Выделение памяти, адресуемой указателем p.";

 р = new int;

 *p = i;

}

myclass::~myclass()

{

 cout <<"Освобождение памяти, адресуемой указателем p.";

 delete p;

}

// Эта функция принимает один объект-параметр.

void display(myclass ob)

{

 cout << ob.getval() << '';

}

Int main()

{

 myclass a(10);

 display(a);

 return 0;

}

Эта программа генерирует такие результаты.

Выделение памяти, адресуемой указателем р.

Вызван конструктор копии.

Освобождение памяти, адресуемой указателем р.

Освобождение памяти, адресуемой указателем р.

При выполнении этой программы здесь происходит следующее: когда в функции main() создается объект а, "стараниями" обычного конструктора выделяется память, и адрес этой области памяти присваивается указателю а.р. Затем объект а передается функции display(), а именно— ее параметру ob. В этом случае вызывается конструктор копии, который создает копию объекта а. Конструктор копии выделяет память для этой копии, а значение указателя на выделенную область памяти присваивает члену р объекта-копии. Затем значение, адресуемое указателем р исходного объекта, записывается в область памяти, адрес которой хранится в указателе р объекта-копии. Таким образом, области памяти, адресуемые указателями а.р и ob.р, раздельны и независимы одна от другой, но хранимые в них значения (на которые указывают а.р и ob.р) одинаковы. Если бы конструктор копии не был определен, то в результате создания по умолчанию побитовой копии члены а.р и ob.р указывали бы на одну и ту же область памяти.

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

Использование конструкторов копии при инициализации объектов

Конструктор копии также вызывается в случае, когда один объект используется для инициализации другого.

Рассмотрим следующую простую программу.

// Вызов конструктора копии для инициализации объекта.

#include <iostream>

#include <cstdlib>

using namespace std;

class myclass {

  int *p;

 public:

  myclass(int i); // обычный конструктор

  myclass(const myclass &ob); // конструктор копии

  ~myclass();

  int getval() { return *p; }

};

// Конструктор копии.

myclass::myclass(const myclass &ob)

{

 p = new int;

 *p = *ob.p; // значение копии

 cout << "Выделение p-памяти конструктором копии.";

}

// Обычный конструктор.

Myclass::myclass(int i)

{

 cout << "Выделение p-памяти обычным конструктором.";

 р = new int;

 *р = i;

}

myclass::~myclass()

{

 cout << "Освобождение р-памяти.";

 delete p;

}

Int main()

{

 myclass a(10); // Вызывается обычный конструктор.

 myclass b = a; // Вызывается конструктор копии.

 return 0;

}

Результаты выполнения этой программы таковы.

Выделение p-памяти обычным конструктором.

Выделение p-памяти конструктором копии.

Освобождение р-памяти.

Освобождение р-памяти.

Как подтверждают результаты выполнения этой программы, при создании объекта а вызывается обычный конструктор. Но когда объект а используется для инициализации объекта b, вызывается конструктор копии. Использование конструктора копии гарантирует, что объект b выделит для своих членов данных собственную область памяти. Без конструктора копии объект b попросту представлял бы собой точную копию объекта а, а член а.р указывал бы на ту же самую область памяти, что и член b.р.

Следует иметь в виду, что конструктор копии вызывается только в случае выполнения инициализации. Например, следующая последовательность инструкций не вызовет конструктор копии, определенный в предыдущей программе:

myclass а(2), b(3);

// ...

b = а;

Здесь инструкция b = а выполняет операцию присваивания, а не операцию копирования.

Использование конструктора копии при возвращении функцией объекта

Конструктор копии также вызывается при создании временного объекта, который является результатом возвращения функцией объекта. Рассмотрим следующую короткую программу.

/* Конструктор копии вызывается в результате создания временного объекта в качестве значения, возвращаемого функцией.

*/

#include <iostream>

using namespace std;

class myclass {

 public:

  myclass() { cout << "Обычный конструктор."; }

  myclass(const myclass &obj) {cout << "Конструктор копии."; }

};

Myclass f()

{

 myclass ob; // Вызывается обычный конструктор.

 return ob; // Неявно вызывается конструктор копии.

}

Int main()

{

 myclass a; // Вызывается обычный конструктор.

 а = f(); // Вызывается конструктор копии.

 return 0;

}

Эта программа генерирует такие результаты.

Обычный конструктор.

Обычный конструктор.

Конструктор копии.

Здесь обычный конструктор вызывается дважды: первый раз при создании объекта а в функции main(), второй — при создании объекта ob в функции f(). Конструктор копии вызывается в момент, когда генерируется временный объект в качестве значения, возвращаемого из функции f().

Хотя "скрытые" вызовы конструкторов копии могут отдавать мистикой, нельзя отрицать тот факт, что практически каждый класс в профессионально написанных программах содержит явно определенный конструктор копии, без которого не избежать побочных эффектов, возникающих в результате создания по умолчанию побитовых копий объекта.

Конструкторы копии — а нельзя ли найти что-то попроще?

Как уже неоднократно упоминалось в этой книге, C++ — очень мощный язык. Он имеет множество средств, которые наделяют его широкими возможностями, но при этом его можно назвать сложным языком. Конструкторы копии представляют собой механизм, на который ссылаются многие программисты как на основной пример сложности языка, поскольку это средство не воспринимается на интуитивном уровне. Начинающие программисты часто не понимают, почему так важен конструктор копии. Для многих не сразу становится очевидным ответ на вопрос: когда нужен конструктор копии, а когда — нет. Эта ситуация часто выражается в такой форме: "А не существует ли более простого способа?". Ответ также непрост: и да, и нет!

Такие языки, как Java и С#, не имеют конструкторов копии, поскольку ни в одном из них не создаются побитовые копии объектов. Дело в том, что как Java, так и C# динамически выделяют память для всех объектов, а программист оперирует этими объектами исключительно через ссылки. Поэтому при передаче объектов в качестве параметров функции или при возврате их из функций в копиях объектов нет никакой необходимости.

Тот факт, что ни Java, ни C# не нуждаются в конструкторах копии, делает эти языки проще, но за простоту тоже нужно платить. Работа с объектами исключительно посредством ссылок (а не напрямую, как в C++) налагает ограничения на тип операций, которые может выполнять программист. Более того, такое использование объектных ссылок в Java и C# не позволяет точно определить, когда объект будет разрушен. В C++ же объект всегда разрушается при выходе из области видимости.

Язык C++ предоставляет программисту полный контроль над ситуациями, складывающимися в программе, поэтому он несколько сложнее, чем Java и С#. Это — цена, которую мы платим за мощность программирования.

Ключевое слово this

Ключевое слово this — это указатель на объект, который вызывает функцию-член.

При каждом вызове функции-члена ей автоматически передается указатель, именуемый ключевым словом this, на объект, для которого вызывается эта функция. Указатель this — это неявный параметр, принимаемый всеми функциями-членами. Следовательно, в любой функции-члене указатель this можно использовать для ссылки на вызывающий объект.









Читайте также:

Последнее изменение этой страницы: 2016-03-17; Просмотров: 108;


lektsia.info 2017 год. Все права принадлежат их авторам! Главная