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