Почему С++ 11 имеет неявные ходы для параметров значений, но не для параметров rvalue?

В С++ 11 значения параметров (и других значений) имеют неявный ход при возврате:

A func(A a) {
    return a; // uses A::A(A&&) if it exists
}

Как минимум в MSVC 2010, для ссылочных параметров rvalue требуется std::move:

A func(A && a) {
    return a; // uses A::A(A const&) even if A::A(A&&) exists
}

Я бы предположил, что внутри функций, ссылка на rvalue и значение ведут себя одинаково, с той лишь разницей, что в случае значений сама функция несет ответственность за разрушение, тогда как для ссылок rvalue ответственность за пределами.

Какова мотивация для их обработки по-разному в стандарте?

Ответы

Ответ 1

Комитет по стандартизации потратил большие усилия на создание формулировок, чтобы движения происходили только в двух случаях:

  • Если это безопасно, сделайте это.
  • Когда пользователь явно запрашивает (через std::move или аналогичный перевод).

Параметр значения, несомненно, будет уничтожен в конце функции. Поэтому возвращение его движением явно безопасно; он не может быть затронут другим кодом после возврата (если только вы не намеренно пытаетесь сломать вещи, и в этом случае вы, вероятно, вызвали поведение undefined). Поэтому он может быть перемещен из возврата.

A && переменная может относиться к временному. Но это может быть ссылка на lvalue (именованная переменная). Поэтому нецелесообразно переходить от него; первоначальная переменная может скрываться. И так как вы явно не просили перейти от него (т.е. Вы не вызывали std::move в этой функции), движение не может происходить.

Единственный раз, когда переменная && будет неявно перемещена из (т.е. без std::move), когда вы ее возвращаете. std::move<T> возвращает a T&&. Для этого возвращаемого значения является законным, чтобы вызвать конструктор перемещения, потому что это возвращаемое значение.

Теперь очень сложно вызвать A func(A &&a) с lvalue без вызова std::move (или эквивалентного перевода). Так что технически это должно быть хорошо для параметров типа &&, которые должны быть неявно перемещены. Но комитет по стандартам хотел, чтобы действия были явными для типов &&, чтобы убедиться, что движение не косвенно происходит в рамках этой функции. То есть он не может использовать знание вне функции о том, откуда приходит &&.

В общем случае вы должны принимать только параметры && в двух случаях: либо вы пишете конструктор перемещения (или переместите оператор присваивания, но даже это можно сделать по значению), либо вы пишете пересылку функция. Могут быть несколько других случаев, но вы не должны брать && в тип, если у вас нет чего-то особенного. Если A является подвижным типом, то просто возьмите его по значению.

Ответ 2

В вашем первом случае компилятор знает, что a уходит, и ничто не сможет его цепляться: ясно, что этот объект можно перенести, и если он не будет уничтожен. Во втором случае ссылка rvalue указывает, что разрешено перемещаться из объекта, и вызывающий объект не ожидает, что объект останется вокруг. Однако выбор функции зависит от того, использует ли это это разрешение или нет, и могут быть причины, по которым функция иногда хочет перейти от аргумента, а иногда и не хочет. Если компилятору была предоставлена ​​свобода перемещения с этого объекта, не было бы способа предотвратить компилятор. Однако, используя std::move(a), уже существует способ указать, что желательно перейти от объекта.

Общее правило в стандарте заключается в том, что компилятор только когда-либо перемещает объекты, которые, как известно, исчезают. Когда приходит ссылка rvalue, компилятор действительно не знает, что объект близок к: если он был явно std::move() ed, он фактически остается вокруг.

Ответ 3

Это было исправлено для С++ 20 с помощью P0527 и P1825. Единственный способ связать параметр функции со ссылкой на rvalue - это чтобы источник был временным, или чтобы вызывающая сторона явно приводила не временное значение к rvalue (например, с помощью std::move). Поэтому эта "обязательная оптимизация" считалась безопасной.