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().

Шпаргалка

Конструктор перемещения передает ресурсы от временного объекта вместо их копирования, что позволяет избежать дорогостоящего глубокого копирования. Он принимает 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;
}
quiz iconПроверьте себя

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

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