Оператор присваивания с перемещением (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, поэтому при присвоении будет срабатывать оператор присвоения с перемещением.
Стоит отметить, что компилятор сам компилирует оператор присваивания с перемещением по умолчанию, который перемещает значения всех нестатических переменных. Однако если мы определяем деструктор или конструктор копирования или конструктор перемещения или оператор присваивания, то компилятор не генерирует стандартный оператор присваивания с перемещением.
Поскольку 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 }
Стоит отметить, что после того, как мы переместим значение из указателя, мы не сможем получить значения по данному указателю.