С++. Конструктор копирования. Примеры использования. Передача объекта класса в функцию. Возврат класса из функции

С++. Конструктор копирования. Примеры использования. Передача объекта класса в функцию. Возврат класса из функции

В данной теме рассмотрена работа конструктора копирования на примере unmanaged (native) классов. При рассмотрении данной темы рекомендуется прочитать тему:


Содержание


1. Какое назначение конструктора копирования (copy constructor)?

Конструктор копирования – это специальный конструктор, который позволяет получить идентичный к заданному объект. То есть, с помощью конструктора копирования можно получить копию уже существующего объекта. Конструктор копирования еще называется инициализатором копии (copy initializer). Конструктор копирования должен получать входным параметром константную ссылку (&) на объект такого же класса.

 

2. В каких случаях вызывается конструктор копирования?

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

Случай 1. В момент объявления нового объекта и его инициализации данными другого объекта с помощью оператора =. Следующий фрагмент кода демонстрирует данную ситуацию для некоторого класса ClassName

// объявление экземпляра (объекта) класса ClassName
ClassName obj1;

// объявление объекта obj2 с одновременной инициализацией данными объекта obj2
ClassName obj2 = obj1; // вызывается конструктор копирования

В этом случае нужно скопировать данные из объекта obj1 в объект obj2. То есть, нужно создать копию объекта obj1 так, чтобы этот объект мог в дальнейшем корректно использоваться в программе. Поэтому нужна копия. Этим занимается конструктор копирования.

Случай 2. Когда нужно передать объект в функцию как параметр-значение. В этом случае создается полная копия объекта.

Случай 3. Когда нужно вернуть объект из функции по значению. В этом случае также создается полная копия объекта.

 

3. В каких случаях целесообразно использовать конструктор копирования?

Конструктор копирования необходимо использовать в тех классах, где осуществляется динамическое выделение памяти для данных. Другими словами, если в классе есть указатель, для которого память выделяется динамически с помощью оператора new (или других функций), то такой класс обязательно должен иметь конструктор копирования. В противном случае, в программе будут возникать проблемы, связанные с недостатками побитового копирования. Кроме того, такой класс должен содержать операторную функцию operator=(), перегружающую оператор копирования (это уже другая тема).

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



 

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

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

Пример. Пусть задан класс CMyPoint, описывающий точку на координатной плоскости. В классе объявляется несколько конструкторов, в том числе и конструктор копирования.

// класс CMyPoint
class CMyPoint
{
    int x, y;

    public:
    CMyPoint(void); // конструктор класса по умолчанию
    CMyPoint(int nx, int ny); // конструктор класса с двумя параметрами
    CMyPoint(const CMyPoint & ref_Point); // конструктор копирования

    // методы доступа - реализованы в классе
    int GetX(void) { return x; }
    int GetY(void) { return y; }
};

// реализация конструкторов (методов) класса
// конструктор класса CMyPoint
CMyPoint::CMyPoint(void)
{
    x = y = 0;
}

// конструктор класса CMyPoint с двумя параметрами
CMyPoint::CMyPoint(int nx, int ny)
{
    x = nx;
    y = ny;
}

// конструктор копирования класса CMyPoint
// передается ссылка на CMyPoint
CMyPoint::CMyPoint(const CMyPoint & ref_Point)
{
    // копирование данных из одного объекта в другой
    x = ref_Point.x;
    y = ref_Point.y;
}

Демонстрация использования конструктора копирования в некотором программном коде (методе)

// демонстрация использования конструктора копирования
CMyPoint p1(5, 8); // создание объекта p1
CMyPoint p2; // создание объекта p2 - вызывается конструктор по умолчанию

// проверка
int d;
d = p1.GetX(); // d = 5
d = p2.GetX(); // d = 0

p2 = p1; // побитовое копирование, конструктор копирования не вызывается
d = p2.GetX(); // d = 5 - данные были скопированы, но без использования конструктора копирования

// код, который вызывает конструктор копирования
CMyPoint p3 = p1; // инициализация объекта => вызывается конструктор копирования
d = p3.GetX(); // d = 5

 

5. Пример передачи объекта класса в функцию как параметр-значение

В примере передается объект класса CMyPoint (см. п. 4) в функцию GetLength(), которая вычисляет расстояние от точки CMyPoint к началу координат. Текст функции следующий:

// функция, которая вычисляет расстояние от точки до начала координат
// точка есть входящим параметром
double GetLength(CMyPoint mp)
{
    double length;
    int tx, ty;

    tx = mp.GetX();
    ty = mp.GetY();
    length = Math::Sqrt(tx*tx + ty*ty);

    return length;
}

Использование функции в другом программном коде

CMyPoint p1(5,5); // объявить экземпляр класса CMyPoint
double len;

// передача точки p1 в функцию, вызывается конструктор копирования, len = 7,07...
len = GetLength(p1);

 

6. Пример возврата объекта класса из функции по значению с помощью конструктора копирования

Реализовать функцию GetCenterPoint(), которая возвращает точку, которая есть серединой отрезка, проведенного между точкой CMyPoint и началом координат.

Объявление класса точно такое же как в п. 4.

Реализация двух вариантов функций GetCenterPoint() и GetCenterPoint2().

// функция, которая возвращает середину отрезка
CMyPoint GetCenterPoint(CMyPoint mp)
{
    int tx, ty;
    tx = mp.GetX() / 2;
    ty = mp.GetY() / 2;

    // возврат из функции, конструктор копирования не вызывается, 
    // вместо него вызывается конструктор с двумя параметрами
    return CMyPoint(tx, ty);
}

// функция, которая возвращает середину отрезка, заданного точками
CMyPoint GetCenterPoint2(int x, int y)
{
    CMyPoint mp(x/2, y/2);

    // создается временный объект, который инициализируется значением mp,
    // в результате вызывается конструктор копирования
    return mp;

    // в этом случае конструктор копирования не вызывается
    // return CMyPoint(x/2,y/2);
}

Демонстрация использования функций

CMyPoint mp(18,-8);

CMyPoint mpC;

// вызывается конструктор копирования при передаче mp в функцию
mpC = GetCenterPoint(mp); 

int cx, cy;
cx = mpC.GetX(); // cx = 9
cy = mpC.GetY(); // cy = -4

mpC = GetCenterPoint2(-9, 13); // вызывается конструктор копирования
cx = mpC.GetX(); // cx = -4
cy = mpC.GetY(); // cy = 6

В первом варианте GetCenterPoint() конструктор копирования вызывается только при передаче параметра mp по значению. При возврате из функции GetCenterPoint() с помощью оператора return, конструктор копирования не вызывается. Вместо него вызывается конструктор с двумя параметрами, объявленными в классе.

Во втором варианте GetCenterPoint2() конструктор копирования вызывается при возвращении из функции оператором return. В операторе return создается временный объект класса CMyPoint, который сразу инициализируется значением mp, в результате этого вызывается конструктор копирования.

 

7. Как осуществляется копирование объектов, когда в классе отсутствует конструктор копирования?

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

Побитовое копирование есть приемлемым для классов, в которых нет динамического выделения памяти. Однако, если в классе есть динамическое выделение памяти (класс использует указатели), то побитовое копирование приведет к тому, что указатели обоих объектов будут указывать на один и тот же участок памяти. А это ошибка.

 


Связанные темы