Почему при возврате вызывается конструктор копирования вместо конструктора перемещения? - PullRequest
9 голосов
/ 25 июня 2019

Допустим, у меня есть класс MyClass с правильным конструктором перемещения и конструктор копирования которого удален.Теперь я возвращаю этот класс следующим образом:

MyClass func()
{
    return MyClass();
}

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

Теперь скажем, MyClass имеетреализация оператора <<:

MyClass& operator<<(MyClass& target, const int& source)
{
    target.add(source);
    return target;
}

Когда я изменяю приведенный выше код:

MyClass func()
{
    return MyClass() << 5;
}

Я получаю ошибку компилятора, что не удается получить доступ к конструктору копирования, поскольку онудален.Но почему в этом случае вообще используется конструктор копирования?

Ответы [ 3 ]

15 голосов
/ 25 июня 2019

Теперь я возвращаю этот класс через lvalue следующим образом:

MyClass func()
{
    return MyClass();
}

Нет, возвращаемое выражение - xvalue (разновидность rvalue), используемое для инициализации результата для возврата по значению (все немного сложнее, начиная с C ++ 17, но это все еще суть этого, кроме того, вы находитесь на C ++ 11).

В этом случае конструктор перемещения вызывается при возврате объекта класса, и все работает как положено.

Действительно, rvalue инициализирует ссылку rvalue, и, таким образом, все это может соответствовать конструкторам перемещения.

Когда я изменяю код выше:

& hellip; теперь выражение MyClass() << 5 имеет тип MyClass&. Это никогда не будет ценным. Это lvalue. Это выражение ссылается на существующий объект.

Итак, без явного std::move, это будет использоваться для copy-initialise результата. И, поскольку ваш конструктор копирования удален, это не может работать.


Я удивлен, что пример компилируется вообще, поскольку временная переменная не может использоваться для инициализации ссылки на lvalue (первый аргумент вашего оператора), хотя известно, что некоторые цепочки инструментов (MSVS) принимают это как расширение.


тогда вернется std::move(MyClass() << 5); работа?

Да, я верю в это.

Однако на это очень странно смотреть, и он заставляет читателя перепроверить, чтобы не было оборванных ссылок. Это говорит о том, что есть лучший способ сделать это, что приведет к более ясному коду:

MyClass func()
{
    MyClass m;
    m << 5;
    return m;
}

Теперь вы все еще получаете ход (потому что это специальное правило при return использовании локальных переменных ) без каких-либо странных выходок. И, в качестве бонуса, << звонок полностью соответствует стандартам.

8 голосов
/ 25 июня 2019

Ваш оператор вернется на MyClass&. Таким образом, вы возвращаете lvalue, а не rvalue, который может быть перемещен автоматически.

Вы можете избежать копирования, полагаясь на стандартные гарантии, касающиеся NRVO.

MyClass func()
{
    MyClass m;
    m << 5;
    return m;
}

Это либо полностью исключит объект, либо переместит его. Все из-за того, что это локальный объект функции.


Другой вариант, учитывая, что вы пытаетесь вызвать operator<< для значения r, заключается в предоставлении перегрузки, обрабатывающей ссылки на значения rvalue.

MyClass&& operator<<(MyClass&& target, int i) {
    target << i; // Reuse the operator you have, here target is an lvalue
    return std::move(target);
}

Это сделает MyClass() << 5 хорошо сформированным (см. Другой ответ, почему это не так) и вернет xvalue, из которого может быть создан возвращаемый объект. Хотя такие и перегрузки для operator<< обычно не видны.

1 голос
/ 25 июня 2019

Ваш operator<< принимает свой первый параметр как неконстантную ссылку.Вы не можете привязать неконстантную ссылку к временному.Но MyClass() возвращает вновь созданный экземпляр как временный.

Кроме того, в то время как func возвращает значение, operator<< возвращает ссылку.Так что еще он может сделать, кроме как сделать копию для возврата?

...