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

Является ли конструктор перемещения дважды повторяющимся в С++?

Посмотрите на этот код:

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 ### ###

4b9b3361

Ответ 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.

...

[Примечание. Это двухступенчатое разрешение перегрузки должно выполняться независимо от того, произойдет ли копирование.. Он определяет конструктор, который будет вызываться, если исключение не выполняется, а выбранный конструктор должен быть доступен, даже если вызов отменен. - конечная нота ]