Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 20 из 104.

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

Конструктор копирования принимает константную ссылку на объект того же класса:

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // Конструктор копирования
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // Вызывается конструктор копирования
Player another(original);  // Также вызывает конструктор копирования

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

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // Конструктор глубокого копирования
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // Выделение новой памяти
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // Копирование содержимого
    }
    
    ~Buffer() { delete[] data; }
};

Без пользовательского конструктора копирования оба объекта будут указывать на одну и ту же область памяти. Когда один из них будет уничтожен, у другого останется висячий указатель. Глубокое копирование выделяет новую память и копирует фактические данные, гарантируя, что каждый объект владеет собственными ресурсами.

challenge icon

Задание

Легко

Давайте создадим систему истории сообщений, которая демонстрирует важность глубокого копирования, когда ваш класс управляет динамической памятью. Вы создадите класс MessageLog, который хранит сообщения в динамически выделенном массиве, и реализуете правильный конструктор копирования, чтобы каждая копия имела свою собственную независимую память.

Для организации кода вы создадите два файла:

  • MessageLog.h: Определите класс MessageLog, который управляет коллекцией сообщений с использованием динамической памяти. Ваш класс должен содержать:
    • Приватные члены: указатель на массив строк для хранения сообщений, capacity для максимального количества сообщений и count для текущего количества сообщений
    • Параметризованный конструктор, который принимает емкость, выделяет массив в куче и инициализирует count значением 0
    • Конструктор копирования, который выполняет глубокое копирование — выделяет новую память и копирует все сообщения из исходного объекта
    • Деструктор, который освобождает выделенную память и выводит "MessageLog destroyed"
    • Метод addMessage(string msg), который добавляет сообщение, если есть место
    • Метод getCount(), который возвращает текущее количество сообщений
    • Метод getMessage(int index), который возвращает сообщение по заданному индексу
  • main.cpp: Продемонстрируйте, что ваш конструктор копирования создает независимую копию. Считайте значение емкости и два сообщения из входных данных, затем:
    • Создайте объект MessageLog с именем original с указанной емкостью
    • Добавьте оба сообщения в original
    • Создайте копию с именем backup, используя конструктор копирования
    • Добавьте третье сообщение "New message" только в original
    • Выведите "Original count: <count>"
    • Выведите "Backup count: <count>"
    • Выведите все сообщения из backup, по одному в строке

Формат входных данных:

  • Первая строка: емкость (целое число)
  • Вторая строка: первое сообщение (строка)
  • Третья строка: второе сообщение (строка)

Если ваше глубокое копирование реализовано правильно, у backup должно быть 2 сообщения, в то время как у original — 3, что доказывает наличие у них независимой памяти. Без правильного конструктора копирования оба объекта использовали бы один и тот же массив, и изменения в одном влияли бы на другой!

Подключите ваш заголовочный файл в main.cpp с помощью #include "MessageLog.h".

Шпаргалка

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

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // Конструктор копирования
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // Вызывается конструктор копирования
Player another(original);  // Также вызывает конструктор копирования

Если вы не определите конструктор копирования, компилятор создаст его автоматически. Такой конструктор выполняет поверхностное копирование (shallow copy) — прямое копирование значений каждого члена класса. Это приводит к проблемам, когда ваш класс управляет динамической памятью.

Конструктор глубокого копирования выделяет новую память и копирует фактические данные, гарантируя, что каждый объект владеет собственными ресурсами:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // Конструктор глубокого копирования
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // Выделение новой памяти
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // Копирование содержимого
    }
    
    ~Buffer() { delete[] data; }
};

Без пользовательского конструктора копирования оба объекта будут указывать на одну и ту же область памяти, что приведет к появлению «висячих» указателей при уничтожении одного из объектов.

Попробуйте сами

#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;

int main() {
    // Считать входные данные
    int capacity;
    cin >> capacity;
    cin.ignore();
    
    string msg1, msg2;
    getline(cin, msg1);
    getline(cin, msg2);

    // TODO: Создать MessageLog с именем 'original' с заданной вместимостью capacity

    // TODO: Добавить оба сообщения в original

    // TODO: Создать копию с именем 'backup', используя конструктор копирования

    // TODO: Добавить "New message" только в original

    // TODO: Вывести "Original count: <count>"

    // TODO: Вывести "Backup count: <count>"

    // TODO: Вывести все сообщения из backup, по одному на строку

    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование