ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В C++ (4-Е ИЗДАНИЕ) (часть 11) онлайн

Конструктор копирования

Мы уже обсуждали, что можно определять объекты и одновременно их инициа-

лизировать другими с помощью двух видов выражений:

alpha аЗ(а2);  //инициализация копирования

alpha аЗ = а2; //инициализация копирования.

               // альтернативная запись

Оба стиля определения включают в себя конструктор копирования, то есть

конструктор, создающий новый объект и копирующий в него свои аргументы.

По умолчанию этот конструктор производит поэлементное копирование. Это

очень похоже на то, что делает оператор присваивания, с той лишь разницей,

что конструктор создает новый объект.

Как и оператор присваивания, конструктор копирования может быть пере-

гружен. В программе XOFXREF мы покажем, как это делается.

Листинг 11.17. Программа XOFXREF

// xofxref.cpp

// конструктор копирования: X(X&)

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class alpha

  {

  private:

    int data;

  public:

    alpha()            //конструктор без аргументов

 

      { }

    alpha(int d)       //конструктор с одним аргументом

      { data = d; }

    alpha(alpha& a)    //конструктор копирования

      {

      data = a.data;

      cout << "\nЗапущен конструктор копирования";

      }

    void display()     //display

      { cout << data; }

    void operator = (alpha& a) //overloaded = operator

      {

      data = a.data;

      cout << "\nЗапущен оператор присваивания";

      }

  };

///////////////////////////////////////////////////////////

int main()

{

  alpha a1(37);

  alpha a2;

 

  a2 = a1;             //запуск перегружаемого =

  cout << "\na2="; a2.display();  //вывести a2

 

  alpha a3(a1);        //запуск конструктора копирования

// alpha a3 = a1;      //эквивалентное определение a3

  cout << "\na3="; a3.display();  //вывести a3

  cout << endl;

  return 0;

}

В этой программе перегружается и оператор присваивания, и конструктор

копирования. Перегружаемое присваивание подобно аналогичному из програм-

мы ASSIGN. У конструктора копирования имеется один аргумент — объект типа

alpha, переданный по ссылке. Вот его описатель:

alpha(alpha& a)

Он имеет вид Х(Х&). Ниже приведен результат работы программы:

Запущен оператор присваивания

а2=37

Запущен конструктор копирования

а3=37

Выражение

а2 = a1;

запускает оператор присваивания, тогда как

alpha a3(a1);

запускает конструктор копирования. Для последней цели может быть использо-

вано другое эквивалентное выражение:

alpha аЗ = a1;

 

Как уже было показано, конструктор копирования может быть запущен во

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

функциям по значению и когда возвращается результат работы функций. Давай-

те кратко рассмотрим эти ситуации.

Аргументы функции

Итак, конструктор копирования запускается при передаче по значению аргумен-

та функции. Он создает копию объекта, с которой функция и работает. Таким

образом, если бы функция

void func(alpha);

была объявлена в XOFXREF и если бы она вызывалась выражением

func(a1);

то конструктор копирования создал бы копию объекта a1 для его использова-

ния func(). Само собой разумеется, что конструктор копирования не запускается

при передаче аргумента по ссылке или при передаче указателя на объект. В этих

случаях никаких копий не создается, и функция работает с исходной перемен-

ной.

Возврат результатов работы функции

Конструктор копирования также создает временный объект, когда значение воз-

вращается из функции в основную программу. Допустим, что в XOFXREF была ка-

кая-либо подобная функция:

alpha func();

и она вызывалась выражением

a2 = func();

Конструктор копирования в этом случае запустился бы для того, чтобы соз-

дать копию возвращаемого функцией func() результата, а затем, после запуска

оператора присваивания, этот результат был бы сохранен в а2.

Почему не просто x(x)?

А надо ли нам использовать ссылку в аргументе конструктора копирования?

Может быть, можно просто передать значение? Но нет, компилятор сообщает о

«выходе за пределы памяти» при попытке откомпилировать выражение

alpha(alpha а)

Почему? Да потому, что при передаче аргумента по значению создается его

копия. Кто ее создает? Конструктор копирования. Но мы ведь и запускаем кон-

структор копирования, то есть он пытается запустить самого себя, поймать соб-

ственный хвост, до тех пор, пока не выйдет за пределы памяти. Так что в конст-

руктор копирования аргумент должен передаваться по ссылке, что не приведет

к созданию копий объекта.

 

Следите за деструкторами

В разделах «Передача по ссылке» и «Возврат значений» мы обсуждали передачу

функциям аргументов по значению и возврат результатов в вызывающую про-

грамму по значению. В этих ситуациях также запускается деструктор, когда при

выходе из функции уничтожаются временные объекты. Это может вызвать недо-

умение, если вы не ожидаете такого поворота событий. Мораль сей басни такова:

работая с объектами, которым требуется нечто большее, нежели простое поэле-

ментное копирование, передавайте и возвращайте данные по ссылкам везде, где

это возможно.

Определение и конструктора копирования,

и оператора присваивания

Перегружая оператор присваивания, вы почти наверняка намереваетесь заодно

переопределить и конструктор копирования (и наоборот). Дело в том, что вам не

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

ны, есть места в программах, где они необходимы. Даже если вы не предполага-

ете использовать в программе одно либо другое, можно заметить, что компилятор

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

например, передача аргумента по значению или возврат результата по значению.

Фактически, если конструктор класса включает в себя операции по работе с

системными ресурсами — памятью, файлами на диске, — вам следует почти все-

гда перегружать и конструктор копирования, и оператор присваивания, не забы-

вая убедиться в том, что они работают корректно.

Запрещение копирования

Мы обсудили, как настроить процедуру копирования объектов с помощью кон-

структора копирования и оператора присваивания. Но иногда бывает нужно за-

претить копирование. Сделать это можно, используя те же инструменты. Напри-

мер, может быть важно то, что каждый член класса создается с уникальным

значением определенного элемента данных, который поставляется в качестве ар-

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

ей будет присвоено то же значение, и объект перестанет быть уникальным. Что-

бы избежать копирования, просто-напросто перегружайте присваивание и кон-

структор копирования, делайте их скрытыми членами класса:

class alpha

{

private:

alpha& operator = (alpha&); //Скрытое присваивание

alpha(alpha&); //Скрытое копирование

};

Как только вы попытаетесь скопировать информацию

alpha a1,а2;

a1 = а2;                //присваивание

alpha a3(a1);     //копирование

 

компилятор сообщит, что функция недоступна. Вам нет никакой необходимости

определять функции, которые никогда не будут вызваны.

 

22