Когда вызывается конструктор копирования в C++?

«Когда вызывается конструктор копирования в C++?» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

  1. Явная инициализация копией:
    MyClass obj1;
    MyClass obj2(obj1);      // Прямой вызов конструктора копирования
    MyClass obj3 = obj1;     // Копирующая инициализация (также вызывает конструктор копирования)
  2. Передача аргумента в функцию по значению:
    void foo(MyClass param); // Функция принимает объект по значению
    MyClass obj;
    foo(obj);                // При вызове `param` создаётся копированием `obj`
  3. Возврат объекта из функции по значению (в определённых условиях):
    MyClass createObject() {
        MyClass localObj;
        return localObj;     // До C++17 мог вызываться конструктор копирования/перемещения.
                             // Начиная с C++17, здесь гарантировано применяется оптимизация
                             // (mandatory copy elision), и вызова конструктора не происходит.
    }
  4. Инициализация элементов или базовых классов: При создании объекта, члены которого или базовые классы инициализируются другим объектом.

Практический пример:

#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource defaultn"; }
    Resource(const Resource&) { std::cout << "Resource copyn"; }
};

class MyClass {
    Resource res;
public:
    MyClass() { std::cout << "MyClass defaultn"; }
    MyClass(const MyClass& other) : res(other.res) { // Вызов конструктора копирования Resource
        std::cout << "MyClass copyn";
    }
};

void byValue(MyClass c) { /* ... */ }

int main() {
    std::cout << "--- Creating original ---n";
    MyClass original;

    std::cout << "n--- Copy initialization ---n";
    MyClass copy = original; // Вызов MyClass::MyClass(const MyClass&)

    std::cout << "n--- Passing by value ---n";
    byValue(original);       // Ещё один вызов конструктора копирования
    return 0;
}

Важно: Современный C++ (C++11 и новее) стремится заменять копирование на перемещение (через конструктор перемещения) там, где это возможно, например, при возврате локальной переменной из функции.