C++11 Оптимизация возвращаемого значения или переезд?

Я не понимаю, когда я должен использовать std::move и когда я должен позволить компилятору оптимизировать... например:

using SerialBuffer = vector< unsigned char >;

// let compiler optimize it
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    // Return Value Optimization
    return buffer;
}

// explicit move
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    return move( buffer );
}

Какой я должен использовать?

4 ответа

Решение

Используйте исключительно первый метод:

Foo f()
{
  Foo result;
  mangle(result);
  return result;
}

Это уже позволит использовать конструктор перемещения, если таковой имеется. Фактически, локальная переменная может связываться со ссылкой на rvalue в return Заявление точно, когда разрешено копирование.

Ваша вторая версия активно запрещает копировать разрешение. Первая версия универсально лучше.

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

Компиляторы могут автоматически перемещать возвращаемое значение (чтобы оптимизировать копию) и даже оптимизировать перемещение!

Раздел 12.8 стандарта N3337 (C++11):

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

[...]

Пример:

class Thing {
public:
Thing();
   ~Thing();
   Thing(const Thing&);
};

Thing f() {
   Thing t;
   return t;
}

Thing t2 = f();

Здесь критерии для исключения могут быть объединены, чтобы исключить два вызова конструктора копирования класса Thing: копирование локального автоматического объекта t во временный объект для возвращаемого значения функции f() и копирование этого временного объекта в объект t2, Эффективно, строительство местного объекта t может рассматриваться как непосредственная инициализация глобального объекта t2и уничтожение этого объекта произойдет при выходе из программы. Добавление конструктора перемещения в Thing имеет тот же эффект, но это перемещение конструкции из временного объекта в t2 это исключено. - конец примера ]

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

Это довольно просто.

return buffer;

Если вы сделаете это, то либо произойдет NRVO, либо нет. Если этого не произойдет, то buffer будет перемещен из.

return std::move( buffer );

Если вы это сделаете, то NVRO не произойдет, и buffer будет перемещен из.

Так что нечего приобретать, используя std::move здесь и многое потерять.

Из этого правила есть одно исключение:

Buffer read(Buffer&& buffer) {
    //...
    return std::move( buffer );
}

Если buffer это ссылка Rvalue, то вы должны использовать std::move, Это потому, что ссылки не имеют права на NRVO, поэтому без std::move это приведет к копированию из lvalue.

Это просто пример правила "всегда move Rvalue ссылки и forward универсальные ссылки ", который имеет приоритет над правилом" никогда move возвращаемое значение ".

Если вы возвращаете локальную переменную, не используйте move(), Это позволит компилятору использовать NRVO, и в противном случае компилятору все равно будет разрешено выполнить перемещение (локальные переменные становятся R-значениями внутри return заявление). С помощью move() в этом контексте просто запретил бы NRVO и заставил бы компилятор использовать перемещение (или копию, если перемещение недоступно). Если вы возвращаете что-то отличное от локальной переменной, NRVO в любом случае не вариант, и вам следует использовать move() если (и только если) вы намерены воровать объект.

Другие вопросы по тегам