MyTetra Share
Делитесь знаниями!
Оператор присваивания с перемещением в языке 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 (&copy != 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

}



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


Так же в этом разделе:
 
MyTetra Share v.0.65
Яндекс индекс цитирования