Возвращает ли локальный объект семантику перемещения?
При возврате локального объекта по значению компиляторы С++ могут оптимизировать ненужные копии (копировать elision), используя семантику перемещения.
" может оптимизировать" подразумевает, что если надлежащие условия не выполняются, поведение должно возвращаться к семантике возврата по значениям по умолчанию на основе копии.
Таким образом, как я понимаю, всегда верна копируемый объект по значению.
Но компиляторы (clang и gcc), похоже, не согласны с моей интерпретацией, как показано ниже MWE.
class Foo {
public:
Foo();
Foo(const Foo&);
Foo(Foo&&) = delete;
}
Foo f() { return Foo(); } // error: call to explicitly deleted constructor of 'Foo'
Foo g() { Foo a; return a; } // gcc complains, clang is fine
Foo x = g(); // error: call to explicitly deleted constructor of 'A'
Q1: Требуется ли возврат по значению объекта для перемещения?
Q2: Если нет, делайте gcc и clang misbehave на моем MWE или я пропущу что-то еще?
Ответы
Ответ 1
Вы просто отвечаете намеченному поведению разрешения перегрузки: Foo()
является rvalue, поэтому разрешение перегрузки находит конструктор Foo(Foo&&)
лучшим совпадением. Поскольку эта перегрузка удалена, ваша программа плохо сформирована. Кроме того, существует специальное правило, в котором говорится, что Foo a; return a;
также будет выполнять перегрузку, как если бы a
было первым значением rvalue. (Это правило применяется, когда оператор возврата имеет право на копирование.)
Это все работает по назначению. Вы удалили перегрузку, поэтому вы прямо сказали, что такие конструкции запрещены.
Обратите внимание, что "настоящий" код обычно не встречает этого препятствия, так как как только вы объявляете конструктор копии, ваш класс вообще не будет иметь никаких конструкторов перемещения. Но вы ушли с вашего пути, чтобы сказать: "Нет, на самом деле мне нужен конструктор перемещения, и я хочу, чтобы это была ошибка, если кто-то пытается ее использовать".
Ответ 2
Относительно этого:
Foo g() { Foo a; return a; } // gcc complains, clang is fine
GCC прав, это не должно компилироваться из-за [class.copy]/32
(выделение мое):
Когда критерии для исключения операции копирования/перемещения выполняются, но не для объявления исключения, и объект, подлежащий копированию, обозначается lvalue или когда выражение в выражении return
является (возможно, в скобках) id-выражение, которое называет объект с автоматическим временем хранения, объявленным в теле или параметр-объявление-предложение самой внутренней функции включения или лямбда-выражения, разрешение перегрузки для выбора конструктора для копии сначала выполняется так, как если бы объект был обозначен rvalue. Если первое разрешение перегрузки выходит из строя или не выполняется, или если тип первого параметра выбранного конструктор не является ссылкой rvalue для типа объектов (возможно, с квалификацией cv), разрешение перегрузки выполняется снова, рассматривая объект как lvalue. [Примечание: Это двухступенчатое разрешение перегрузки должно быть независимо от того, произойдет ли копирование. Он определяет конструктор, который будет вызываться, если elision не выполняется, и выбранный конструктор должен быть доступен, даже если вызов отменен. -end note]
Таким образом, реализация должна выбрать move constructor для elision, и при ее удалении программа плохо сформирована.