Menu
Coddy logo textTech

Конструктор перемещения

Часть раздела Объектно-ориентированное программирование путешествия по 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.

challenge icon

Задание

Легко

Давайте создадим систему пакетов данных, которая демонстрирует, как конструкторы перемещения эффективно передают владение ресурсами. Вы создадите класс 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().

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

#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
    // Подсказка: используйте std::move()

    // TODO: Вывести "After move:"

    // TODO: Вывести "Original - ID: <id>, Size: <size>, Sum: <sum>"

    // TODO: Вывести "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

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

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

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