Как вызвать конструктор перемещения c
Перейти к содержимому

Как вызвать конструктор перемещения c

  • автор:

Как вызвать конструктор перемещения c

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

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

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

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

#include // класс сообщения 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]; >std::cout // обычный оператор присваивания Message& operator=(const Message& copy) < std::cout ; i < size; i++) // копируем данные < text[i] = copy.text[i]; >> return *this; // возвращаем текущий объект > // опрератор присваивания с перемещением Message& operator=(Message&& moved) < std::cout return *this; // возвращаем текущий объект > // деструктор ~Message() < std::cout char* getText() const < return text; >unsigned getSize() const < return size; >unsigned getId() const private: char* text<>; // текст сообщения unsigned size<>; // размер сообщения unsigned id<>; // номер сообщения static inline unsigned counter<>; // статический счетчик для генерации номера объекта >; int main() < char text1[] ; Message hello; char text2[] ; hello = Message; // присваивание объекта std::cout

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

Message& operator=(Message&& moved) < std::cout return *this; // возвращаем текущий объект >

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

char text2[] ; hello = Message;

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

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; Message hi; hello = hi; // присвоение lvalue - обычный оператор присваивания hello = Message; // присвоение rvalue - оператор присваивания с перемещением

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

Message hello; Message hi; hello = std::move(hi); // преобразование lvalue в rvalue - оператор присваивания с перемещением

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

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

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

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

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

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

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

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

Как вызвать конструктор перемещения c

Конструктор перемещения (move constructor) представляет альтернативу конструктору копирования в тех ситуациях, когда надо сделать копию объекта, но копирование данных нежелательно — вместо копирования данных они просто перемещаются из одной копии объекта в другую.

Рассмотрим на примере.

#include // класс сообщения 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]; >std::cout // конструктор копирования Message(const Message& copy) : Message // обращаемся к стандартному конструктору < std::cout // деструктор ~Message() < std::cout char* getText() const < return text; >unsigned getSize() const < return size; >unsigned getId() const private: char* text<>; // текст сообщения unsigned size<>; // размер сообщения unsigned id<>; // номер сообщения static inline unsigned counter<>; // статический счетчик для генерации номера объекта >; // класс мессенджера, который отправляет сообщение class Messenger < public: Messenger(Message mes): message < >void sendMessage() const < std::cout private: Message message; >; int main() < Messenger telegram>; telegram.sendMessage(); >

Здесь определен класс условного сообщения Message. Текст сообщения хранится в символьном указателе text. Также, чтобы был виден весь процесс создания/копирования/удаления данных в классе сообщения определена статическая переменная counter, которая будет увеличиваться с созданием каждого нового объекта. И текущее значение счетчика будет присваиваться переменной id, которая представляет номер сообщения:

char* text<>; // текст сообщения unsigned size<>; // размер сообщения unsigned id<>; // номер сообщения static inline unsigned counter<>;

В конструкторе Message выделяем память для хранения текста сообщения, который передается через параметр — символьный массив, копируем данные в выделенную область памяти и устанавливаем номер сообщения. Для копирования данных в Message определен конструктор копирования.

Также определен класс Messenger, который принимает в конструкторе сообщение и сохраняет его в переменную message:

class Messenger < public: Messenger(Message mes): message

С помощью функции sendMessage мессенджер условно отправляет сообщение.

В функции main создаем объект Messenger, передавая ему один объект сообщения, и затем вызываем функцию sendMessage

Messenger telegram>; telegram.sendMessage();

Обратите внимание, что в конструктор объекта Messenger здесь передается объект Message, который не привязан ни к какой переменной. Посмотрим на консольный вывод:

Create message 1 Create message 2 Copy message 1 to 2 Delete message 1 Send message 2: Hello Word Delete message 2

Здесь мы видим, что в процессе работы программы создается два объекта Message, причем вовлекается конструктор копирования. Посмотрим по этапно.

Messenger telegram>;

Приводит к выполнению конструктора Message, в котором строка «Hello Word» передается переменной text и устанавливает номер сообщения. Этот временный объект Message будет иметь номер 1. Соответственно на консоль выводится

Create message 1

Messenger(Message mes): message

Обратите внимание на выражение message . Оно берет переданный в конструктор временный объект Message и с помощью конструктора копирования передает в переменную message его копию. Конструктор копирования Message в свою очередь обращается к стандартному конструктору:

Message(const Message& copy) : Message

Создается объект Message номер 2. Стандартный конструктор выделяет память для строки. У нас получаются две копии, каждая из которых хранит указатели на разные участки памяти. То есть мы имеем две независимые копии, и на консоль будет выведено:

Create message 2 Copy message 1 to 2 Delete message 1
telegram.sendMessage();

Информация о хранимом в мессенджере сообщении выводится на консоль, и это сообщение по завершению работы функции main удаляется.

Send message 2: Hello Word Delete message 2

С точки зрения создания копий, выделения/управления/освобождения памяти вроде проблем никаких нет. Но мы видим, что выделенная память для первого сообщения в итоге все равно никак не использовалась. То есть мы по сути зря использовали эту память. Не было бы лучше, если бы вместо выделения нового участка памяти для второго сообщения, мы могли бы просто передать ему память, которая уже выделена для первого сообщения? Первое же сообщение все равно удаляется. И для решения этой проблемы как раз используются конструкторы перемещения.

Конструктор перемещения принимает один параметр, который должен представлять ссылку rvalue на объект текущего типа:

MyClass(MyClass&& moved) // ссылка rvalue < // код конструктора перемещения >

Здесь параметр moved представляет перемещаемый объект.

Изменим выше приведенный код, применив конструктор перемещения в классе Message:

#include // класс сообщения 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]; >std::cout // конструктор копирования Message(const Message& copy) : Message // обращаемся к стандартному конструктору < std::cout Message(Message&& moved) < std::cout // деструктор ~Message() < std::cout char* getText() const < return text; >unsigned getSize() const < return size; >unsigned getId() const private: char* text<>; // текст сообщения unsigned size<>; // размер сообщения unsigned id<>; // номер сообщения static inline unsigned counter<>; // статический счетчик для генерации номера объекта >; // класс мессенджера, который отправляет сообщение class Messenger < public: Messenger(Message mes): message < >void sendMessage() const < std::cout private: Message message; >; int main() < Messenger telegram>; telegram.sendMessage(); >

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

Message(Message&& moved) < std::cout << "Create Message " << id << std::endl; text = moved.text; // перемещаем текст сообщения size = moved.size; // перемещаем размер сообщения moved.text = nullptr; std::cout

Здесь параметр moved представляет перемещаемый объект. Мы не вызваем стандартный конструктор, как в случае с конструктором копирования, потому что нам не надо выделять память. Вместо этого мы просто передаем в переменную text значение указателя (адрес блока выделенной памяти) из перемещаемого объекта moved:

text = moved.text

Таким образом мы избегаем ненужного дополнительного выделения памяти. И чтобы указатель text перемещаемого объекта moved перестал указывать на эту область памяти, и соответственно чтобы в деструкторе объекта moved не было ее освобождения, передаем указателю значение nullptr .

Другой важный момент — в конструкторе Messenger при копировании объекта используем встроенную функцию std::move() , которая имеется в стандартной библиотеке С++:

class Messenger < public: Messenger(Message mes): message

Функция std::move() преобразует переданное значение в ссылку rvalue. Несмотря на свое название, эта функция ничего не перемещает.

Выражение message фактически приведет к вызову конструктора перемещения, в который будет передан параметр mes. А результат конструктора перемещения будет присвоен переменной message. Соответственно теперь мы получим другой консольный вывод:

Create message 1 Create Message 2 Move message 1 to 2 Delete message 1 Send message 2: Hello Word Delete message 2

Если бы конструктор перемещения не был бы определен, то выражение message вызывало бы конструктор копирования.

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

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

Пример с векторами

Еще одним показательным примером применения конструктора перемещение может служить добавление объекта в вектор. Тип std::vector представляет динамический список и для добавления объекта определяет функцию push_back() . Эта функция имеет две версии:

void push_back(const Message &_Val) void push_back(Message &&_Val)

Первая версия принимает константную ссылку и предназначена прежде всего для передачи lvalue. Вторая версия принимает rvalue.

Например, возьмем вышеопределенный класс Message и добавим один объект в вектор:

#include #include // класс сообщения class Message < // содержимое класса Message >int main() < std::vectormessages<>; messages.push_back(Message); >

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

Create Message 1 Create Message 2 Move Message 1 to 2 Delete Message 1 Delete Message 2

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

Если же мы передадим в функцию push_back() значение lvalue, то будет вызываться другая версия функции, которая принимает константную ссылку, и в итоге будет вызываться конструктор копирования с созданием копии:

int main() < Message mes; std::vector messages<>; messages.push_back(mes); >

Консольный вывод программы:

Create message 1 Create message 2 Copy message 1 to 2 Delete message 2 Delete message 1

Конструкторы move и операторы присваивания move (C++)

В этом разделе описывается запись конструктора перемещения и оператора назначения перемещения для класса C++. Конструктор перемещения позволяет переместить ресурсы, принадлежащие объекту rvalue, в lvalue без копирования. Дополнительные сведения о семантике перемещения см. в статье Rvalue Reference Declarator: &>.

Этот раздел построен на основе приведенного ниже класса C++ MemoryBlock , который управляет буфером памяти.

// MemoryBlock.h #pragma once #include #include class MemoryBlock < public: // Simple constructor that initializes the resource. explicit MemoryBlock(size_t length) : _length(length) , _data(new int[length]) < std::cout // Destructor. ~MemoryBlock() < std::cout std::cout // Copy constructor. MemoryBlock(const MemoryBlock& other) : _length(other._length) , _data(new int[other._length]) < std::cout // Copy assignment operator. MemoryBlock& operator=(const MemoryBlock& other) < std::cout return *this; > // Retrieves the length of the data resource. size_t Length() const < return _length; >private: size_t _length; // The length of the resource. int* _data; // The resource. >; 

В следующих процедурах описывается создание конструктора перемещения и оператора присваивания перемещения для этого примера класса C++.

Создание конструктора перемещения для класса C++

  1. Определите пустой метод конструктора, принимающий в качестве параметра ссылку rvalue на тип класса, как показано в следующем примере:
MemoryBlock(MemoryBlock&& other) : _data(nullptr) , _length(0)
_data = other._data; _length = other._length; 
other._data = nullptr; other._length = 0; 

Создание оператора присваивания перемещения для класса C++

  1. Определите пустой оператор присваивания, принимающий в качестве параметра ссылку rvalue на тип класса и возвращающий ссылку на тип класса, как показано в следующем примере:
MemoryBlock& operator=(MemoryBlock&& other)
if (this != &other)
// Free the existing resource. delete[] _data; 

Выполните шаги 2 и 3 из первой процедуры, чтобы переместить данные-члены из исходного объекта в создаваемый объект:

// Copy the data pointer and its length from the // source object. _data = other._data; _length = other._length; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other._data = nullptr; other._length = 0; 
return *this; 

Пример. Полный конструктор перемещения и оператор назначения

В следующем примере показаны полные конструктор перемещения и оператор назначения перемещения для класса MemoryBlock :

// Move constructor. MemoryBlock(MemoryBlock&& other) noexcept : _data(nullptr) , _length(0) < std::cout // Move assignment operator. MemoryBlock& operator=(MemoryBlock&& other) noexcept < std::cout return *this; > 

Пример использования семантики перемещения для повышения производительности

В следующем примере показано, как семантика перемещения может повысить производительность приложений. В примере добавляются два элемента в объект-вектор, а затем вставляется новый элемент между двумя существующими элементами. Класс vector использует семантику перемещения для эффективного выполнения операции вставки, перемещая элементы вектора вместо копирования.

// rvalue-references-move-semantics.cpp // compile with: /EHsc #include "MemoryBlock.h" #include using namespace std; int main() < // Create a vector object and add a few elements to it. vectorv; v.push_back(MemoryBlock(25)); v.push_back(MemoryBlock(75)); // Insert a new element into the second position of the vector. v.insert(v.begin() + 1, MemoryBlock(50)); > 

В примере получается следующий вывод.

In MemoryBlock(size_t). length = 25. In MemoryBlock(MemoryBlock&&). length = 25. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(size_t). length = 75. In MemoryBlock(MemoryBlock&&). length = 75. Moving resource. In MemoryBlock(MemoryBlock&&). length = 25. Moving resource. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 0. In MemoryBlock(size_t). length = 50. In MemoryBlock(MemoryBlock&&). length = 50. Moving resource. In MemoryBlock(MemoryBlock&&). length = 25. Moving resource. In MemoryBlock(MemoryBlock&&). length = 75. Moving resource. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 25. Deleting resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 75. Deleting resource. 

Перед Visual Studio 2010 в этом примере выводятся следующие выходные данные:

In MemoryBlock(size_t). length = 25. In MemoryBlock(const MemoryBlock&). length = 25. Copying resource. In ~MemoryBlock(). length = 25. Deleting resource. In MemoryBlock(size_t). length = 75. In MemoryBlock(const MemoryBlock&). length = 25. Copying resource. In ~MemoryBlock(). length = 25. Deleting resource. In MemoryBlock(const MemoryBlock&). length = 75. Copying resource. In ~MemoryBlock(). length = 75. Deleting resource. In MemoryBlock(size_t). length = 50. In MemoryBlock(const MemoryBlock&). length = 50. Copying resource. In MemoryBlock(const MemoryBlock&). length = 50. Copying resource. In operator=(const MemoryBlock&). length = 75. Copying resource. In operator=(const MemoryBlock&). length = 50. Copying resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 25. Deleting resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 75. Deleting resource. 

Версия этого примера, в которой используется семантика перемещения, более эффективна, чем версия, в которой эта семантика не используется, поскольку в ней выполняется меньше операций копирования, выделения памяти и освобождения памяти.

Отказоустойчивость

Во избежание утечки ресурсов (таких как память, дескрипторы файлов и сокеты) обязательно освобождайте их в операторе присваивания перемещения.

Чтобы предотвратить невосстановимое уничтожение ресурсов, в операторе присваивания перемещения необходимо правильно обрабатывать присваивания самому себе.

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

// Move constructor. MemoryBlock(MemoryBlock&& other) noexcept : _data(nullptr) , _length(0)

Функция std::move преобразует lvalue other в rvalue.

Конструктор перемещения

Конструктор и оператор перемещения используются компилятором в разных ситуациях:

  • конструктор перемещения применяется в местах, где объявление совпадает с определением (инициализацией) rvalue-ссылкой на экземпляр этого же класса, либо посредством direct initialization в конструкторе класса/структуры (если же определение произойдет с помощью lvalue-ссылки, то вызовется конструктор копирования);
  • оператор перемещения применяется в местах, где экземпляр класса уже был ранее определен и к нему применяется operator = , который в качестве аргумента приминает rvalue-ссылку на экземпляр этого же класса (если же оператор принимает lvalue-ссылку , то вызовется оператор присваивания).

Про rvalue-ссылки можете почитать здесь, здесь и здесь.

Контрольный пример (для разъяснения отличия в работе данных конструкций)

#include class Buffer < public: Buffer(const std::string& buff) : pBuff(nullptr) , buffSize(buff.length()) < pBuff = new char[buffSize]; memcpy(pBuff, buff.c_str(), buffSize); >~Buffer() < destroy(); >Buffer(const Buffer& other) : pBuff(nullptr) , buffSize(other.buffSize) < pBuff = new char[buffSize]; memcpy(pBuff, other.pBuff, buffSize); >Buffer& operator=(const Buffer& other) < destroy(); buffSize = other.buffSize; pBuff = new char[buffSize]; memcpy(pBuff, other.pBuff, buffSize); return *this; >Buffer(Buffer&& tmp) : pBuff(tmp.pBuff) , buffSize(tmp.buffSize) < tmp.pBuff = nullptr; >Buffer& operator=(Buffer&& tmp) < destroy(); buffSize = tmp.buffSize; pBuff = tmp.pBuff; tmp.pBuff = nullptr; return *this; >private: void destroy() < if (pBuff) delete[] pBuff; >char* pBuff; size_t buffSize; >; Buffer CreateBuffer(const std::string& buff) < Buffer retBuff(buff); return retBuff; >int main() < Buffer buffer1 = CreateBuffer("123"); // срабатывает конструктор перемещения Buffer buffer2 = buffer1; // срабатывает конструктор копирования buffer2 = CreateBuffer("123"); // срабатывает конструктор перемещения, затем оператор перемещения buffer2 = buffer1; // срабатывает оператор присваивания >

Дополнение

В C++11 каждый класс, помимо конструктора по умолчанию, имеет следующие 5 дефолтных операций:

  1. конструктор копирования (copy constructor);
  2. оператор присваивания (copy assignment);
  3. конструктор перемещения (move constructor);
  4. оператор перемещения (move assignment);
  5. деструктор (destructor).

При определении одной из этих 5-ти операций рекомендуется явно указать (либо определить, либо объявить с помощью default или delete ) все остальные, т.к. все эти 5 операций тесно связаны. Это будет способствовать лучшему пониманию семантики класса при чтении кода.
Если явно определена одна из упомянутых 5-ти операций (в том числе с использованием default или delete ), то:

  • недостающие операции копирования будут определены автоматически с поведением по умолчанию;
  • недостающие операции перемещения определены не будут.

Это следует учитывать при написании классов.

Отслеживать
ответ дан 9 фев 2016 в 23:58
StateItPrimitive StateItPrimitive
7,769 1 1 золотой знак 25 25 серебряных знаков 47 47 бронзовых знаков

Вопрос фактически звучит как «зачем вообще нужны конструкторы». Вопрос в принципе не относится к «перемещениям», а фактически сводится к принципиальной разнице между конструкторами (копирования, перемещения, и т.д.) и другими функциями-членами класса (операторы присваивания и т.д.)

Конструктор в общем случае работает на «сыром» (несконструированном, непроинициализорованном) блоке памяти. В момент начала работы конструктора объекта как такового еще не существует и он не имеет никакого предсказуемого состояния. Соответственно работа конструктора сводится к созданию/инициализации нового объекта в предоставленном блоке «сырой» памяти. Конструктор копирования, например, копирует это состояние из некоего объекта-образца, конструктор перемещения — перемещает, конструктор преобразования — преобразует и т.д. Конструктор перемещения никоим образом не выделяется из этого ряда.

Оператор присваивания же всегда имеет дело с уже проинициализированным/сконструированным объектом, находящимся в некоем предсказуемом «валидном» состоянии. Работа оператора присваивания сводится к освобождению исходного состояния объекта (освобождению ресурсов, например), за которым следует копирование (или перемещение, или преобразование и т.п.) нового состояния из некоего объекта-источника.

Вот собственно и все. Т.е. операторы присваивания в общем случае делают больше работы, чем конструкторы. Операторы присваивания уничтожают старое состояние объекта и создают новое. А конструкторам уничтожать нечего — они только создают новое состояние.

В рамках этой логики как конструктор перемещения, так и перемещающий оператор присваивания никак из общего ряда не выделяются. Поэтому не ясно, откуда вообще мог возникнуть вопрос вроде «Зачем нужен конструктор перемещения, если есть оператор перемещения?».

Отслеживать
ответ дан 10 фев 2016 в 1:49
AnT stands with Russia AnT stands with Russia
69.4k 3 3 золотых знака 65 65 серебряных знаков 140 140 бронзовых знаков

В принципе, стандартная реализация оператора присваивания могла бы делать следующее: для объекта в левой части оператора присваивания вызвать сначала деструктор, а потом конструктор копирования, передав ему ссылку на объект в правой части. Наверное, это было бы лучше, чем просто копировать все поля из правого объекта в левый, но всё равно это было бы не совсем правильно. Дело в том, что при копировании объекта требуется выделить новый ресурс, но потенциально это может закончиться неудачей. Если это произойдёт, то после неудачной попытки присваивания объект в левой части останется в некорректном состоянии: старый ресурс уже освобождён (вызван деструктор), а новый захватить не получилось. Поэтому при перегрузке оператора присваивания нужно всегда сначала захватывать новый ресурс, и только если эта операция прошла успешно, освобождать старый, заменяя его новым (по этой причине оператор присваивания в принятом ответе написан не совсем правильно). В случае перемещения, в принципе, такой проблемы нет: копируется только ссылка на ресурс, и старый ресурс, которым владел объект в левой части оператора перемещения, может быть освобождён безболезненно, как до копирования ссылки, так и после. Скорее всего, аналогичное копированию разделение на конструктор и оператор в случае перемещения сделано для случая, когда неудачей закончилось освобождение ресурса в левой части оператора перемещения. В этой ситуации необходимо, чтобы объект в правой части продолжал владеть ресурсом. Таким образом сохраняется атомарность копирования и перемещения: либо нам удалось полностью создать копию объекта или полностью переместить ресурс из одного объекта в другой, либо копирование / перемещение прошло неудачно, и ни один из объектов не был изменён.

Отслеживать
ответ дан 18 ноя 2018 в 20:57
8,895 26 26 серебряных знаков 67 67 бронзовых знаков

  • c++
  • c++11
    Важное на Мете
Связанные
Похожие

Подписаться на ленту

Лента вопроса

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

Дизайн сайта / логотип © 2024 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2024.4.30.8420

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *