Что происходит с объектом после std::move?

149
30 октября 2019, 20:10

Пример класса:

class Move
{
   public:
   Move(std::string name) : name(name) {};
   Move(Move&& move) : name(std::move(move.name)) {};
   void Set(std::string&& add)
   {
      this->name.append(add);
   }
   void Print()
   {
      std::cout << this->name << std::endl;
   }
private:
   std::string name;
};

После выполнения данного кода:

Move move0("string");
Move move1 = std::move(move0);

объект move0 будет иметь вид {name=""}, но "" пустая std::string все равно занимает место в памяти и не освобождает ее сама по себе. Получается, что перемещение более эффективное в некоторых случаях, но при этом старые ссылки остаются "доживать" свой век? Не понятно, что происходит с объектами после использования на них std::move, хотя казалось бы, что они должны становиться nullptr.

UPD

Почему есть ситуации в которых после std::move объект из которого было произведено перемещение, остается без изменений?

int main()
{
   std::string str = " Another";
   m.Set(std::move(str));
   return 0;
}

После std::move(str), сам str остался без изменений.

Answer 1

С++ - это вам не Java. В нем объекты не могут быть null, и не являются ссылками.

что происходит с объектами после использования на них std::move

Строго говоря, move ничего не делает, кроме как превращает выражение в rvalue ("маскирует" под временную переменную). Это влияет в первую очередь на разрешение перегрузок, и в том числе заставляет срабатывать перемещающие конструктор и оператор присваивания, вместо копирующих. Изменения в объекте вызываются именно перемещающими конструктором или оператором присваивания, куда он передается как аргумент (если это вообще происходит).

Что конкретно происходит с объектом после перемещения из него - зависит того, как написаны перемещающие конструктор и оператор присваивания.

Про стандартные контейнеры, например, говорят что они "are left in valid, but unspecified state". В большинстве реализаций они становятся пустыми, но это не гарантируется.

казалось бы, что они должны становиться nullptr.

Объекты нельзя "сделать nullptr", но можно переформулировать вопрос:

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

Я так понимаю, потому что это очень усложнило бы работу компилятора.

Например, для

void foo(std::string s)
{
    // ...
}

деструктор всегда вызывается для s при возврате из функции.

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

Answer 2

В языке С++ нет никакой встроенной семантики перемещения. В языке С++ есть только rvalue-ссылки, категории результатов выражений, и связанные с ними правила overload resolution. А что вы сами "слепите" из этих базовых свойств языка - семантику перемещения или что-то другое - определяется только вашим кодом (или, соответственно, библиотечным кодом).

Вы передали переменную str в метод Set по rvalue-ссылке, ни внутри метода Set вы только копируете полученный аргумент add и никакого физического перемещения данных из него даже и не пытаетесь делать.

Если вы "надеетесь" получить физическое перемещение из add внутри метода Set, то это ваша задача - разрешить методу std::string::append переместить данные из своего аргумента

void Set(std::string&& add)
{
  this->name.append(std::move(add));
}

Но и в этом случае фактическое перемещение данных из add произойдет только в том случае, если сам метод std::string::append примет решение выполнить перемещение. И здесь я вам могу сказать по секрету, что метод std::string::append в принципе никогда ничего не перемещает. В большинстве применений std::string::append это просто-напросто невозможно. Оптимизировать std::string::append через физическое перемещение возможно только в том случае, когда строка-получатель изначально пуста. А это слишком частная ситуация, чтобы ее оптимизировать. (Я бы лично, возможно, это сделал, но разработчики класса std::string рассудили по-другому.)

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

READ ALSO
Класс String. Перегрузка операторов

Класс String. Перегрузка операторов

Помогите найти ошибку в коде

151
Не компилируется GDI+ через cl.exe

Не компилируется GDI+ через cl.exe

Пытаюсь собрать пример скриншота на GDI+ на С++ через clexe Вот пример который я пытаюсь собрать

148
Результат a += a++

Результат a += a++

Почему после выполнения такого кода

156