V828. Moving an object in a return statement may prevent copy elision.
Анализатор обнаружил ситуацию, когда локальная переменная, временный объект или параметр функции возвращаются из функции посредством вызова 'std::move'.
Рассмотрим пример:
struct T { .... };
T foo()
{
T t;
// ....
return std::move(t);
}
На первый взгляд может показаться, что такой код более оптимизирован, так как первым будет гарантировано выбран конструктор перемещения, однако это не так. Использование 'std::move' в контексте возвращаемого выражения может запретить компилятору устранить вызов конструктора копирования / перемещения (copy elision, C++17) и применить RVO/NRVO для локальных объектов.
До появления семантики перемещения (C++11) компиляторы старались производить так называемую оптимизацию возвращаемого значения ([Named] Return Value Optimization) в обход вызова конструктора копирования, при которой возвращаемый объект создавался непосредственно в стеке вызывающей функции, а затем инициализировался вызванной функцией.
Такую оптимизацию компилятор может сделать лишь в том случае, если возвращаемый тип функции не является ссылкой, а операндом оператора 'return' является имя локальной не-'volatile' переменной, его тип должен совпадает с возвращаемым типом функции (игнорируя 'const' / 'volatile' квалификаторы).
Начиная с C++11, при возвращении из функции не-'volatile' локальной переменной компилятор попробует применить RVO/NRVO, затем конструктор перемещения, и лишь затем конструктор копирования. Поэтому, следующий код работает медленнее, чем ожидается:
struct T { .... };
T foo()
{
T t;
// ....
return std::move(t); // <= V828, pessimization
}
В случае не-'volatile' формального параметра компилятор не может применить RVO/NRVO из-за технических ограничений, но попробует выбрать сначала конструктор перемещения, а затем конструктор копирования. Поэтому, следующий код содержит избыточный вызов функции 'std::move', который можно опустить:
struct T { .... };
T foo(T param)
{
T t;
// ....
return std::move(param); // <= V828, redundant 'std::move' call
}
Также, начиная с C++17, если возвращаемое выражение имеет категорию prvalue (например, результат выполнения функции, которая возвращает не ссылку), то компилятор обязан оптимизировать код, удалив вызов конструктора копирования / перемещения (copy elision). Поэтому, следующий код работает медленнее, чем ожидается:
struct T { .... };
T bar();
T foo()
{
return std::move(bar()); // <= V828, pessimization
}
Во всех представленных случаях рекомендуется удалить вызов функции 'std::move' в целях оптимизации или устранения лишнего кода.
Дополнительная информация:
- Стандарт С++20 (working draft N4860), пункт 11.10.5
- C++ Core Guidelines F.48: Do not return std::move(local)