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()
если (и только если) вы намерены воровать объект.