Является ли конструктор перемещения дважды повторяющимся в С++?
Посмотрите на этот код:
class Foo
{
public:
string name;
Foo(string n) : name{n}
{
cout << "CTOR (" << name << ")" << endl;
}
Foo(Foo&& moved)
{
cout << "MOVE CTOR (moving " << moved.name << " into -> " << name << ")" << endl;
name = moved.name + " ###";
}
~Foo()
{
cout << "DTOR of " << name << endl;
}
};
Foo f()
{
return Foo("Hello");
}
int main()
{
Foo myObject = f();
cout << endl << endl;
cout << "NOW myObject IS EQUAL TO: " << myObject.name;
cout << endl << endl;
return 0;
}
Вывод:
[1] CTOR (Hello)
[2] MOVE CTOR (перемещение Hello в → )
[3] DTOR Hello
[4] MOVE CTOR (перемещение Hello ### в - > )
[5] DTOR Hello ###
[6] СЕЙЧАС ДВА ЕСТЬ РАВНО: Здравствуйте, ### ###
[7] DTOR Hello ### ###
Важное примечание.. Я отключил оптимизацию исключения копий с помощью -fno-elide-constructors
для целей тестирования.
Функция f() создает временную [1] и возвращает ее, вызывающую конструктор перемещения, чтобы "переместить" ресурсы из этого временного объекта в myObject [2] ( кроме того, он добавляет 3 символа).
В конце концов временное разрушено [3].
Теперь я ожидаю, что myObject будет полностью сконструирован, а его атрибут name будет Hello ###.
Вместо этого конструктор перемещения вызывается AGAIN, поэтому я остался с Hello ### ###
Ответы
Ответ 1
Два вызова конструктора:
- Переместить временное созданное
Foo("Hello")
в возвращаемое значение.
- Переместить временный номер, возвращенный вызовом
f()
, в myObject
.
Если для построения возвращаемого значения был использован список бит-init-init, будет только одна конструкция перемещения:
Foo f()
{
return {"Hello"};
}
Выводится:
CTOR (Hello)
MOVE CTOR (moving Hello into -> )
DTOR of Hello
NOW myObject IS EQUAL TO: Hello ###
DTOR of Hello ###
Живая демонстрация
Ответ 2
Поскольку вы отключили копирование, ваш объект сначала создается в f()
, а затем перемещается в замещающее значение для f()
. В этот момент f
локальная копия уничтожается. Затем возвращаемый объект перемещается в myObject
, а также уничтожается. Наконец myObject
уничтожается.
Если вы не отключили копирование, вы бы увидели последовательность, которую вы ожидали.
UPDATE: для ответа на вопрос в комментарии ниже, который задан с помощью определения такой функции:
Foo f()
{
Foo localObject("Hello");
return localObject;
}
Почему конструктор перемещения, вызываемый при создании объекта возвращаемого значения с отключенным копированием? В конце концов, localObject выше - lvalue.
Ответ заключается в том, что компилятор обязан в этих обстоятельствах рассматривать локальный объект как rvalue, поэтому эффективно он неявно генерирует код return std::move(localObject)
. Правило, которое требует, чтобы это было сделано, находится в стандарте [class.copy/32] (выделены соответствующие части):
Когда критерии для выполнения операции копирования/перемещения выполняются, но не для объявления исключения, и объект, который нужно скопировать, обозначается lvalue или , когда выражение в выражении return является (возможно, в скобках) id-выражением, которое называет объект с время автоматического хранения, указанное в теле или параметр-декларация-предложение самой внутренней закрывающей функции или лямбда-выражение, разрешение перегрузки, чтобы выбрать конструктор для копия сначала выполняется так, как если бы объект был назначен Rvalue.
...
[Примечание. Это двухступенчатое разрешение перегрузки должно выполняться независимо от того, произойдет ли копирование.. Он определяет конструктор, который будет вызываться, если исключение не выполняется, а выбранный конструктор должен быть доступен, даже если вызов отменен. - конечная нота ]