Почему перемещение переменной-указателя не устанавливает ее в нуль? - PullRequest
39 голосов
/ 26 февраля 2012

При реализации конструкторов перемещения и операторов присваивания перемещения часто пишется такой код:

p = other.p;
other.p = 0;

Неявно определенные операции перемещения будут реализованы с помощью кода, подобного следующему:

p = std::move(other.p);

Что было бы неправильно, потому что перемещение переменной-указателя не устанавливает ее в ноль. Это почему? В каких случаях мы хотели бы, чтобы операции перемещения оставили исходную переменную-указатель без изменений?

Примечание. Под "перемещением" я подразумеваю , а не , просто подразумевая std::move(other.p), я имею в виду все выражение p = std::move(other.p). Итак, почему не существует специального языкового правила, которое гласит: «Если правая часть присваивания является указателем xvalue, после присвоения оно устанавливается равным нулю.»?

Ответы [ 5 ]

33 голосов
/ 26 февраля 2012

Установка необработанного указателя на ноль после перемещения означает, что указатель представляет владение.Тем не менее, множество указателей используются для представления отношений.Более того, долгое время рекомендуется, чтобы отношения владения были представлены иначе, чем использование необработанного указателя.Например, отношение собственности, на которое вы ссылаетесь, обозначается std::unique_ptr<T>.Если вы хотите, чтобы неявно сгенерированные операции перемещения позаботились о вашем праве собственности, все, что вам нужно сделать, - это использовать членов, которые фактически представляют (и реализуют) желаемое поведение владения.в соответствии с тем, что было сделано с операциями копирования: они также не делают никаких предположений о владельце и не делают, например, глубокого копирования, если указатель копируется.Если вы хотите, чтобы это произошло, вам также нужно создать подходящий класс, кодирующий соответствующую семантику.

6 голосов
/ 26 февраля 2012

Перемещение делает перемещенный объект недействительным. Он не автоматически устанавливает его в безопасное "пустое" состояние. В соответствии с давним принципом C ++ «не плати за то, что не используешь», это ваша работа, если вы этого хотите.

4 голосов
/ 26 февраля 2012

Я думаю, что ответ таков: самостоятельно реализовать такое поведение довольно тривиально, и, следовательно, Стандарт не чувствовал необходимости накладывать какие-либо правила на сам компилятор. Язык C ++ огромен, и не все можно представить перед его использованием. Взять, к примеру, шаблон C ++. Сначала он не был разработан для использования так, как сегодня (т. Е. Это возможность метапрограммирования). Поэтому я думаю, что стандарт просто дает свободу и не устанавливает никаких конкретных правил для std::move(other.p), следуя одному из принципов дизайна: «Вы не платите за то, что не используете» .

Хотя std::unique_ptr является подвижным, но не копируемым. Так что если вы хотите указатель-семантику, которая может быть перемещаемой и копируемой одновременно, то вот одна из тривиальных реализаций:

template<typename T>
struct movable_ptr
{
    T *pointer;
    movable_ptr(T *ptr=0) : pointer(ptr) {} 
    movable_ptr<T>& operator=(T *ptr) { pointer = ptr; return *this; }
    movable_ptr(movable_ptr<T> && other) 
    {
        pointer = other.pointer;
        other.pointer = 0;
    }
    movable_ptr<T>& operator=(movable_ptr<T> && other) 
    {
        pointer = other.pointer;
        other.pointer = 0;
        return *this;
    } 
    T* operator->() const { return pointer; }
    T& operator*() const { return *pointer; }

    movable_ptr(movable_ptr<T> const & other) = default;
    movable_ptr<T> & operator=(movable_ptr<T> const & other) = default;
};

Теперь вы можете писать классы без написания собственной семантики перемещения:

struct T
{
   movable_ptr<A> aptr;
   movable_ptr<B> bptr;
   //...

   //and now you could simply say
   T(T&&) = default; 
   T& operator=(T&&) = default; 
};

Обратите внимание, что вам все еще нужно писать семантику копирования и деструктор, поскольку movable_ptr это , а не умный указатель.

0 голосов
/ 03 июля 2019

Я думаю, что здесь разница - полностью взорванный объект, с одной стороны, и POD, с другой.

  • Для объектов либо разработчик указывает, что должна делать конструкция перемещения и назначение перемещения, либо компилятор генерирует значение по умолчанию. По умолчанию вызывается конструктор перемещения / операторы присваивания всех членов.
  • Для POD (а указатель - POD) C ++ наследуется от C, и с ним ничего не делается, если он явно не закодирован. Это то же поведение, что и члены POD в классе, которые обрабатываются в конструкторе. Если вы явно не поместите их в список инициализаторов, тогда они останутся неинициализированными и станут источником потенциальных ошибок. AFAIK это даже верно для сгенерированных компилятором конструкторов! Вот почему я взял за правило обычно инициализировать всех членов, чтобы они были в безопасности.
0 голосов
/ 26 февраля 2012

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

т.е:.

struct foo
{
  bar*  shared_factory;  // This can be the same for several 'foo's
                         // and must never null.
};

Редактировать

Вот цитата о MoveConstructibe из стандарта:

T u = rv;
...
rv’s state is unspecified [ Note:rv must still meet the requirements
of the library component that is using it. The operations listed in
those requirements must work as specified whether rv has been moved
from or not.
...