Конструктор копирования
Часть раздела Объектно-ориентированное программирование путешествия по 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; }
};Без пользовательского конструктора копирования оба объекта указывали бы на одну и ту же область памяти. Когда один из них был бы уничтожен, у другого остался бы висячий указатель. Глубокое копирование выделяет новую память и копирует фактические данные, гарантируя, что каждый объект владеет собственными ресурсами.
Задание
ЛегкоДавайте создадим систему истории сообщений, которая демонстрирует важность глубокого копирования, когда класс управляет динамической памятью. Вы создадите класс 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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++