Рассмотрим следующий пример:
#include <optional>
#include <string>
struct S {
std::string text = "hello";
};
std::optional<S> foo() {
S s;
return s;
}
std::optional<S> bar() {
S s;
return std::move(s);
}
Я правильно понимаю, что foo
и bar
идентичны, поскольку компилятор выполняет RVO и автоматически перемещает s
при передаче его в std::optional<S>
?
Значит, общее правило «Не перемещайте то, что возвращаете» применяется и тогда, когда произойдет неявное преобразование?
Для чего-то столь тривиального компилятор будет просматривать все насквозь. И он оптимизирует почти все. std::move in bar — это пессимистичный шаг, и вам не следует его делать, он может даже нарушить оптимизацию.
См. этот ответ, в котором объясняется, что операндом оператора return (если он удовлетворяет некоторым условиям) является rvalue(xvalue), а также упомянутое там правило неявного перемещения.
То, что вы здесь описываете, - это не то, что люди обычно подразумевают под «RVO» («оптимизация возвращаемого значения»), хотя этот термин сам по себе является разговорным и не встречается в стандарте.
RVO обычно относится к ситуации до C++17, когда возвращаемое значение prvalue не копируется:
T f();
T g() { return f(); }
В этом примере RVO означает, что при вычислении g()
нет второй копии, и вместо этого результат f()
создается непосредственно в возвращаемом значении g
.
С принятием P0135 для C++17 (несколько неправильно названного, но широко называемого «гарантированным исключением копирования») такое поведение теперь является обязательным, начиная с C++17, поэтому на самом деле не существует «RVO». " больше не называется "оптимизацией" — именно так теперь работает C++. Ключевое концептуальное изменение заключается в том, что prvalues больше не рассматриваются как «материализованные значения» (с хранилищем), а скорее как «инструкции по инициализации объекта».
Однако ничто из этого не имеет отношения к вашему примеру, части которого в просторечии известны как «оптимизация именованного возвращаемого значения» («NRVO»), но это только половина дела. Другая половина заключается в том, что если оптимизация (которая будет состоять из создания значения непосредственно в возвращаемом значении, а не «в стеке») фактически не выполняется, то локальная переменная считается rvalue, когда она появляется как операнд заявление о возврате.
Первоначально в первой версии C++11 такое поведение работало только тогда, когда тип переменной точно соответствовал типу возвращаемого значения функции, но
CWG 1579 задним числом изменил это поведение, так что теперь, начиная с C++11, всегда было так, что возвращаемая переменная считается rvalue, независимо от ее типа (и std::optional
было основной мотивацией для этого исправления). Например, теперь это работает:
std::optional<std::unique_ptr<int>> f() {
auto p = std::make_unique<int>(1);
return p;
}
Со времени C++17 произошли дополнительные изменения, благодаря которым больше вещей «считаются rvalue», когда они появляются как выражение в операторе return или выражении throw, поэтому обычно существует тенденция «пытаться переместить вещи, когда они могут». явно больше не будет использоваться впоследствии"; см. P2266R3, принятый для C++23.
RVO означает что-то другое, но да,
std::move
здесь не нужен.