Понимание С++ 11 rvalues, перемещение семантики и производительности

  Возможный дубликат:
Что произойдет, если я верну литерал вместо объявленного std::string?

Рассмотрим следующий код

string getName () {
    return "meme";
}

string name = getName();

Функция getName() возвращает временный объект. Я понимаю, что в С++ 03 вызывается конструктор копирования string и временный объект уничтожается. На самом деле кажется, что компилятор (по крайней мере в GCC 4.7) оптимизирует строку 5, не создавая объект name, а заменяя его самим временным объектом и не уничтожая временный объект. (Я пытался использовать класс MyVector, а не std::string)

Как определено в стандартах С++ 11,

  1. getName() возвращает значение?

  2. В строке 5 выше, какой конструктор строки вызывается (перемещение или копирование)? Должен ли я обязательно вызывать std::move() для вызова конструктора перемещения?

  3. С семантикой перемещения она менее эффективна, чем оптимизация "копирования", предоставляемая компилятором?

Ответ 1

  • Функции не возвращают значения rvalues ​​или lvalues. Категории значений применяются к выражениям. Таким образом, выражение, которое вызывает функцию, может быть значением rvalue или lvalue. В этом случае выражение getName() является выражением rvalue, потому что функция getName возвращает объект по значению. Это происходит из п. 5.2.2/10:

    Вызов функции - это значение lvalue, если тип результата является ссылочным типом lvalue или ссылкой на тип функции, значение xvalue, если тип результата является ссылкой rvalue для типа объекта, и значение pr в противном случае.

    Тип результата ваших функций не является ссылкой lvalue или rvalue, поэтому вызов функции является значением prvalue. выражения prvalue являются подмножеством выражений rvalue.

  • Будет использоваться конструктор перемещения (если только оно не будет отменено, каким оно может быть). Это потому, что getName() является rvalue, поэтому конструктор std::string, который принимает ссылку rvalue, будет лучше соответствовать аргументу. Обратите внимание, что даже если конструкция перемещения отменена, конструктор перемещения должен быть доступен. То есть, код должен быть скомпилирован, даже если он не удалился.

  • В целом, оптимизация копирования или перемещения elision полностью избавится от любого копирования или перемещения. Так что, конечно, это быстрее, чем на самом деле. Если движение отменяется, буквально ничего не происходит. Для этого не будет никакого кода. Компилятор достигает этого, напрямую конструируя объект в том месте, где он будет скопирован или перенесен.

Стоит отметить, что это также можно было бы оптимизировать одинаково:

string getName () {
  std::string str("meme");
  return str;
}

string name = getName();

Здесь пройдут два шага (включая то, что обычно называют Именованная оптимизация возвращаемого значения). Здесь есть два момента. Во-первых, return str; соответствует критериям для копирования/перемещения elision (§12.8/31):

Это разрешение операций копирования/перемещения, называемое копированием, разрешено в следующих случаях (которые могут быть объединены для устранения нескольких копий):

  • в возвращаемом выражении в функции с типом возвращаемого класса, когда выражение является именем энергонезависимого автоматического объекта (кроме функции или параметра catch-clause) с тем же cv-неквалифицированным типом, что и функция возвращаемый тип, операцию копирования/перемещения можно опустить, построив автоматический объект непосредственно в возвращаемое значение функций
  • ...

Во-вторых, хотя str является lvalue, он все равно будет перемещен из-за того, что он соответствует специальному случаю, заданному стандартом (§12.8/32):

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