Подтвердить что ты не робот

Почему перемещение указательной переменной не указывает на значение null?

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

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

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

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

Что было бы неправильно, потому что перемещение указательной переменной не устанавливает его в null. Почему это? Есть ли случаи, когда мы хотели бы, чтобы операции перемещения оставляли неизменную исходную переменную указателя?

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

4b9b3361

Ответ 1

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

Кроме того, поведение сгенерированных операций перемещения согласуется с тем, что было сделано с операциями копирования: они также не допускают каких-либо допущений в отношении владения и не делают, например. если копия указана. Если вы хотите, чтобы это произошло, вам также необходимо создать подходящий класс, кодирующий соответствующую семантику.

Ответ 2

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

Ответ 3

Я думаю, что ответ заключается в следующем: реализация такого поведения сама по себе довольно тривиальна, и, следовательно, стандарт не чувствовал необходимости навязывать какое-либо правило самому компилятору. Язык С++ огромен, и не все можно представить себе до его использования. Возьмем, к примеру, шаблон С++. Он не был впервые разработан так, как он используется сегодня (т.е. Это возможность метапрограммирования). Поэтому я думаю, что Стандарт просто дает свободу и не делает никакого конкретного правила для 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 - это не умный указатель.

Ответ 4

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

то есть:.

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

Edit

Вот цитата из 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.

Ответ 5

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

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