Menu
CoddyTech

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

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

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

Конструктор копирования принимает константную ссылку на объект того же класса. Это обязательно должна быть ссылка: если передать other по значению, для этого потребуется копия, что приведёт к бесконечному повторному вызову конструктора копирования. Ссылка является const, потому что создание копии никогда не должно изменять оригинал:

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, по одному в строке

Входные данные будут иметь следующий формат:

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

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

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

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

#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' и ёмкостью, заданной во входных данных

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

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

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

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

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

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

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

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор C++