Конструктор копирования
Часть раздела Объектно-ориентированное программирование путешествия по 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; }
};Без пользовательского конструктора копирования оба объекта будут указывать на одну и ту же область памяти. Когда один из них будет уничтожен, у другого останется висячий указатель. Глубокое копирование выделяет новую память и копирует фактические данные, гарантируя, что каждый объект владеет собственными ресурсами.
Задание
ЛегкоДавайте создадим систему истории сообщений, которая демонстрирует важность глубокого копирования, когда ваш класс управляет динамической памятью. Вы создадите класс 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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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Итоги — Обобщенный контейнер