Розглянемо наступний рядок коду:
1 |
int a = 7; |
Тут використовується копіююча ініціалізація для ініціалізації цілочисельної змінної a
значенням 7
. Зі звичайними змінними все просто. Однак з класами все трохи складніше, оскільки в їх ініціалізації використовуються конструктори. На цьому уроці ми розглянемо використання копіюючої ініціалізації з класами.
Використання копіюючої ініціалізації з класами
Розглянемо наступну програму:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <iostream> #include <cassert> class Drob { private: int m_numerator; int m_denominator; public: // Конструктор за замовчуванням Drob(int numerator=0, int denominator=1) : m_numerator(numerator), m_denominator(denominator) { assert(denominator != 0); } friend std::ostream& operator<<(std::ostream& out, const Drob &d1); }; std::ostream& operator<<(std::ostream& out, const Drob &d1) { out << d1.m_numerator << "/" << d1.m_denominator; return out; } int main() { Drob seven = Drob(7); std::cout << seven; return 0; } |
Результат виконання програми:
7/1
Форма копіюючої ініціалізації в мові C++ у вищенаведеному прикладі обробляється так само, як і наступна:
1 |
Drob seven(Drob(7)); |
А, як ми вже знаємо з попереднього уроку, це може призвести до виклику як Drob(int, int)
, так і конструктора копіювання Drob (який може бути проігнорований). Однак, оскільки гарантії на 100% ігнорування конструктора копіювання не надається, то краще уникати використання копіюючої ініціалізації при роботі з класами і замість неї використовувати пряму ініціалізацію або uniform-ініціалізацію, так як у випадку з використанням конструктора копіювання у вас може вийти наступний результат:
7
Замість необхідного:
7/1
Тому що в конструкторі копіювання (який мова C++ надасть автоматично) значення за замовчуванням для m_denominator
не буде.
Правило: Уникайте використання копіюючої ініціалізації при роботі з класами, замість неї використовуйте uniform-ініціалізацію.
Інші застосування копіюючої ініціалізації
Коли ви передаєте або повертаєте об’єкт класу по значенню, то в цьому процесі використовується копіююча ініціалізація. Розглянемо наступну програму:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <iostream> #include <cassert> class Drob { private: int m_numerator; int m_denominator; public: // Конструктор за замовчуванням Drob(int numerator=0, int denominator=1) : m_numerator(numerator), m_denominator(denominator) { assert(denominator != 0); } // Конструктор копіювання Drob(const Drob ©) : m_numerator(copy.m_numerator), m_denominator(copy.m_denominator) { // Немає необхідності виконувати перевірку denominator тут, так як вона здійснюється в конструкторі за замовчуванням std::cout << "Copy constructor worked here!\n"; // просто щоб показати, що це працює } friend std::ostream& operator<<(std::ostream& out, const Drob &d1); int getNumerator() { return m_numerator; } void setNumerator(int numerator) { m_numerator = numerator; } }; std::ostream& operator<<(std::ostream& out, const Drob &d1) { out << d1.m_numerator << "/" << d1.m_denominator; return out; } Drob makeNegative(Drob d) // правильно було б тут використати константне посилання { d.setNumerator(-d.getNumerator()); return d; } int main() { Drob sixSeven(6, 7); std::cout << makeNegative(sixSeven); return 0; } |
Тут функція makeNegative() приймає об’єкт класу Drob по значенню і повертає його так само по значенню. Результат виконання програми:
Copy constructor worked here!
Copy constructor worked here!
-6/7
Перший виклик конструктора копіювання виконається при передачі sixSeven
в якості аргументу в параметр d
функції makeNegative(). Другий виклик виконається при поверненні об’єкта з функції makeNegative() назад в функцію main(). Таким чином, об’єкт sixSeven
копіюється двічі.
У прикладі, наведеному вище, компілятор не може проігнорувати використання конструктора копіювання як при передачі аргументу по значенню, так і при його поверненні. Однак в деяких випадках, якщо аргумент або значення, що повертається, відповідають певним критеріям, компілятор може проігнорувати використання конструктора копіювання. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> class Something { }; Something boo() { Something x; return x; } int main() { Something x = boo(); return 0; } |
В цьому випадку компілятор, швидше за все, проігнорує використання конструктора копіювання, хоча об’єкт x
і повертається по значенню.