Конструктор перемещения
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 21 из 104.
Конструктор перемещения переносит ресурсы из временного объекта вместо их копирования. В то время как конструктор копирования дублирует данные, конструктор перемещения «крадет» ресурсы, оставляя исходный объект в валидном, но пустом состоянии. Это позволяет избежать дорогостоящего глубокого копирования, когда исходный объект все равно скоро будет уничтожен.
Конструктор перемещения принимает rvalue-ссылку, обозначаемую &&:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Конструктор перемещения
Buffer(Buffer&& other) noexcept {
data = other.data; // Заимствуем указатель
size = other.size;
other.data = nullptr; // Оставляем исходный объект в валидном состоянии
other.size = 0;
}
~Buffer() { delete[] data; }
};Ключевое отличие от копирования: мы не выделяем новую память. Мы просто берем на себя владение существующей памятью и устанавливаем указатель источника в nullptr, чтобы его деструктор не удалил наши данные.
Конструкторы перемещения вызываются при инициализации из временных объектов или при использовании std::move():
Buffer createBuffer() {
return Buffer(1000); // Используется конструктор перемещения (возвращаемое значение)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Явное перемещение — b1 теперь пустПомечайте конструкторы перемещения как noexcept, когда это возможно. Это сообщает компилятору, что операция не будет генерировать исключения, что позволяет выполнять важные оптимизации в контейнерах, таких как std::vector.
Задание
ЛегкоДавайте создадим систему пакетов данных, которая демонстрирует, как конструкторы перемещения эффективно передают владение ресурсами. Вы создадите класс DataPacket, который управляет динамически выделенным массивом байтов, и реализуете конструктор перемещения, который «крадет» данные вместо их копирования.
Для организации кода вы создадите два файла:
DataPacket.h: Определите классDataPacket, который управляет полезной нагрузкой из целочисленных данных. Ваш класс должен содержать:- Приватные члены: указатель на целочисленный массив (
payload), размер (size) для количества элементов иpacketId(строка) для идентификации пакета. - Параметризованный конструктор, который принимает ID пакета и размер, выделяет память под массив и заполняет его значениями от 0 до size-1. Выведите
"Packet <id> created with size <size>". - Конструктор перемещения, помеченный как
noexcept, который передает владение полезной нагрузкой от исходного объекта. Выведите"Packet <id> moved". Не забудьте оставить исходный объект в валидном пустом состоянии (nullptr, размер 0). - Деструктор, который освобождает память, если указатель не равен null, и выводит
"Packet <id> destroyed". - Метод
getSize(), который возвращает текущий размер. - Метод
getId(), который возвращает ID пакета. - Метод
getSum(), который возвращает сумму всех элементов в полезной нагрузке (возвращает 0, если payload равен null).
- Приватные члены: указатель на целочисленный массив (
main.cpp: Продемонстрируйте работу конструктора перемещения, считав ID пакета и размер из входных данных. Затем:- Создайте объект
DataPacketс именемoriginal, используя входные значения. - Выведите
"Original - ID: <id>, Size: <size>, Sum: <sum>". - Создайте новый пакет с именем
transferred, переместив данные изoriginalс помощьюstd::move(). - Выведите
"After move:". - Выведите
"Original - ID: <id>, Size: <size>, Sum: <sum>". - Выведите
"Transferred - ID: <id>, Size: <size>, Sum: <sum>".
- Создайте объект
Формат входных данных:
- Первая строка: ID пакета (строка).
- Вторая строка: размер (целое число).
После перемещения исходный пакет должен иметь размер 0 и сумму 0 (так как его данные были переданы), в то время как перемещенный пакет должен содержать все исходные данные. Это демонстрирует, как семантика перемещения позволяет избежать дорогостоящего глубокого копирования путем простой передачи владения указателем.
Подключите заголовочный файл в main.cpp с помощью #include "DataPacket.h" и не забудьте подключить <utility> для использования std::move().
Шпаргалка
Конструктор перемещения передает ресурсы от временного объекта вместо их копирования, что позволяет избежать дорогостоящего глубокого копирования. Он принимает rvalue-ссылку (&&) и «крадет» ресурсы, оставляя исходный объект в валидном, но пустом состоянии.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Конструктор перемещения
Buffer(Buffer&& other) noexcept {
data = other.data; // Крадем указатель
size = other.size;
other.data = nullptr; // Оставляем источник в валидном состоянии
other.size = 0;
}
~Buffer() { delete[] data; }
};Конструкторы перемещения вызываются при инициализации из временных объектов или при использовании std::move():
Buffer createBuffer() {
return Buffer(1000); // Используется конструктор перемещения (возвращаемое значение)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Явное перемещение — b1 теперь пустПомечайте конструкторы перемещения как noexcept, когда это возможно, чтобы включить оптимизации в стандартных контейнерах.
Попробуйте сами
#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"
using namespace std;
int main() {
// Чтение входных данных
string packetId;
int size;
cin >> packetId;
cin >> size;
// TODO: Создайте DataPacket с именем 'original' с использованием входных значений
// TODO: Выведите "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Создайте новый пакет с именем 'transferred', переместив данные из original
// Hint: Используйте std::move()
// TODO: Выведите "After move:"
// TODO: Выведите "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Выведите "Transferred - ID: <id>, Size: <size>, Sum: <sum>"
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Итоги — Обобщенный контейнер