Оператор присваивания с перемещением

Последнее обновление: 24.06.2023

Оператор присваивания с перемещением (move assignment operator) призван решать те же задачи, что и конструктор перемещения. Подобный оператор имеет следующую форму:

MyClass& operator=(MyClass&& moved)
{
	// код оператора
	return *this; // возвращаем текущий объект
}

В качестве параметра передаем перемещаемый объект в виде rvalue-ссылки. В коде оператора выполняем некоторые действия

Определим и используем конструктор присваивания с перемещением:

#include <iostream>
 
// класс сообщения
class Message
{
public:
    // обычный конструктор
    Message(const char* data, unsigned count)
    {
        size = count;
        text = new char[size];  // выделяем память
        for(unsigned i{}; i < size; i++)    // копируем данные
        {
            text[i] = data[i];
        }

        id = ++counter;
        std::cout << "Create Message " << id << std::endl;
    }
    // обычный оператор присваивания
    Message& operator=(const Message& copy)
    {
        std::cout << "Copy assign message " << copy.id << " to " << id << std::endl;
        if (© != this)  // избегаем самоприсваивания
        {
            delete text;        // освобождаем память текущего объекта
            // копируем данные по указателю из перемещаемого объекта в текущий
            size = copy.size;
            text = new char[size];  // выделяем память
            for(unsigned i{}; i < size; i++)    // копируем данные
            {
                text[i] = copy.text[i];
            }
        }
        return *this; // возвращаем текущий объект
    }
    // опрератор присваивания с перемещением
    Message& operator=(Message&& moved)
    {
        std::cout << "Move assign message " << moved.id << " to " << id << std::endl;
        if (&moved != this)     // избегаем самоприсваивания
        {
            delete text;        // освобождаем память текущего объекта
            text = moved.text;  // копируем указатель из перемещаемого объекта в текущий
            size = moved.size;
            moved.text = nullptr; // сбрасываем значение указателя в перемещаемом объекте
            moved.size = 0;
        }
        return *this; // возвращаем текущий объект
    }
    // деструктор
    ~Message()
    { 
        std::cout << "Delete Message "  << id << std::endl;
        delete[] text;  // освобождаем память
    }
    char* getText() const { return text; }
    unsigned getSize() const { return size; }
    unsigned getId() const {return id;}
private:
    char* text{};  // текст сообщения
    unsigned size{};    // размер сообщения
    unsigned id{};  // номер сообщения
    static inline unsigned counter{};   // статический счетчик для генерации номера объекта
};

int main()
{
    char text1[] {"Hello Word"};
    Message hello{text1, std::size(text1)};

    char text2[] {"Hi World!"};
    hello = Message{text2, std::size(text2)};   // присваивание объекта
    std::cout << "Message " <<  hello.getId() << ": " << hello.getText() << std::endl;
}

В операторе присваивания получаем перемещаемый объект Message, удаляем ранее выделенную память и копируем значение указателя из перемещаемого объекта:

Message& operator=(Message&& moved)
{
    std::cout << "Move assign message " << moved.id << " to " << id << std::endl;
    if (&moved != this)     // избегаем самоприсваивания
    {
        delete text;        // освобождаем память текущего объекта
        text = moved.text;  // копируем указатель из перемещаемого объекта в текущий
        size = moved.size;
        moved.text = nullptr; // сбрасываем значение указателя в перемещаемом объекте
        moved.size = 0;
    }
    return *this; // возвращаем текущий объект
}

В функции main присваиваем переменной hello объект Message:

char text2[] {"Hi World!"};
hello = Message{text2, std::size(text2)};

Стоит отметить, что, как и в случае с конструктором перемещения, присваиваемое значение представляет rvalue - временный объект в памяти (Message{text2, std::size(text2)};), который после выполнения операции (присовения) будет не нужен. И это как раз идеальный случай для применения оператора присваивания с перемещением. Консольный вывод данной программы:

Create message 1
Create message 2
Move assign message 2 to 1
Delete message 2
Message 1: Hi World!
Delete message 1

Как видно, переменная hello представляет объект Message с номером 1. Стоит отметить, что если в классе определено несколько операторов присваивания (стандартный и присваивание с перемещением), то по умолчанию для rvalue будет применяться оператор присваивания с перемещением. При присвоении lvalue будет применять стандартный оператор присвоения (без перемещения):

Message hello{"Hello Word", 11};
Message hi{"Hi World!", 10};
hello = hi; // присвоение lvalue - обычный оператор присваивания
hello = Message{"Hi World!", 10}; // присвоение rvalue - оператор присваивания с перемещением

Стоит отметить, что мы можем применить функцию std::move() для преобразования lvalue в rvalue:

Message hello{"Hello Word", 11};
Message hi{"Hi World!", 10};
hello = std::move(hi); // преобразование lvalue в rvalue - оператор присваивания с перемещением

Здесь переменная hi преобразуется в rvalue, поэтому при присвоении будет срабатывать оператор присвоения с перемещением.

Стоит отметить, что компилятор сам компилирует оператор присваивания с перемещением по умолчанию, который перемещает значения всех нестатических переменных. Однако если мы определяем деструктор или конструктор копирования или конструктор перемещения или оператор присваивания, то компилятор не генерирует стандартный оператор присваивания с перемещением.

std::unique_ptr и перемещение значений

Поскольку smart-указатель std::unique_ptr уникально указывает на определенный адрес памяти, не может быть двух и более указателей std::unique_ptr, которые указывают на один и тот же участок памяти. Именно поэтому у типа unique_ptr нет конструктора копирования и оператора присваивания с копирования. Соотвественно при попытки их применить мы столкнемся с ошибками компиляции:

#include <iostream> 
#include <memory>

int main()
{
	std::unique_ptr<int> one{ std::make_unique<int>(123) };
	std::unique_ptr<int> other;
	// other = one; 			// Ошибка! оператор присваивания с копированием отсутствует 
	// std::unique_ptr<int> another{ other }; // Ошибка! конструктор копированием отсутствует 
}

Однако unique_ptr имеет конструктор перемещения и оператор присвоения с перемещением, которые при необходимости перемещения данных из одного указателя в другой можно использовать

#include <iostream> 
#include <memory>

int main()
{
	std::unique_ptr<int> one{ std::make_unique<int>(123) };
	std::unique_ptr<int> other;
	other = std::move(one); 		// оператор копирования с перемещением
	// std::cout << *one << std::endl;  // Данные из one перемещены в other
	std::cout << *other << std::endl;	// 123
	std::unique_ptr<int> another{ std::move(other) }; // конструктор перемещения
	std::cout << *another << std::endl;	// 123
}

Стоит отметить, что после того, как мы переместим значение из указателя, мы не сможем получить значения по данному указателю.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850