С++. Конструктор копіювання. Приклади використання. Передача об’єкту класу в функцію. Повернення класу

С++. Класи. Частина 2. Конструктор копіювання. Приклади використання. Передача об’єкту класу в функцію. Повернення класу з функції

У даній темі розглянуто роботу конструктора копіювання на прикладі unmanaged (native) класів. При розгляді даної теми рекомендується прочитати тему:


Зміст



1. Що таке конструктор копіювання (copy constructor)?

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

 

2. У яких випадках викликається конструктор копіювання?

Конструктор копіювання викликається у випадках, коли потрібно отримати повну копію об’єкту. В C++ необхідність отримання повної копії об’єкту можлива у трьох випадках.

Випадок 1. При оголошенні нового об’єкту та його ініціалізації даними іншого об’єкту з допомогою оператора =. Наступний фрагмент коду демонструє дану ситуацію для деякого класу ClassName

// оголошення екземпляру (об'єкту) obj1 класу ClassName
ClassName obj1;

// оголошення об'єкту obj2 з одночасною ініціалізацією даними об'єкту obj1
ClassName obj2 = obj1; // викликається конструктор копіювання

У цьому випадку потрібно скопіювати дані з об’єкта obj1 в об’єкт obj2. Тобто, потрібно створити копію об’єкту obj1 так, щоб цей об’єкт 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;

    // повернення з функції, конструктор копіювання не викликається
    // замість нього викликається конструктор з 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. Як здійснюється копіювання об’єктів, якщо конструктор копіювання не оголошений у класі?

Якщо у класі не оголошений конструктор копіювання, то використовується конструктор копіювання, який автоматично генерується компілятором. Цей конструктор копіювання здійснює побітове копіювання для отримання копії об’єкта.

Побітове копіювання є коректним для класів, в яких немає динамічного виділення пам’яті. Однак, якщо у класі є динамічне виділення пам’яті (клас використовує покажчики), то побітове копіювання призводить до того, що покажчики двох об’єктів вказують на ту саму ділянку пам’яті. А це є помилка.

 


Зв’язані теми