|
|||||||
Оператор присваивания с перемещением в языке C++
Время создания: 10.12.2024 12:38
Текстовые метки: c++, си++, опрератор, присваивание, перемещение, &&, rvalue, r-value, ссылка
Раздел: Компьютер - Программирование - Язык C++ (Си++) - Стандарт C++11 и выше
Запись: xintrea/mytetra_syncro/master/base/1733823535xgel2c7jv4/text.html на raw.github.com
|
|||||||
|
|||||||
Оператор присваивания с перемещением (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 по rvalue ссылке. Для присвоения, у текущего объекта удаляется ранее выделенная память под хранение текста сообщения, и копируется значение указателя из перемещаемого объекта: 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 } Стоит отметить, что после того, как значение из указателя было перемещено, невозможно получить значение по данному указателю. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|