Понимание оптимизации возвращаемого значения и возврата временных значений — C++

Пожалуйста, рассмотрите три функции.

std::string get_a_string()
{
    return "hello";
}

std::string get_a_string1()
{
    return std::string("hello");
}

std::string get_a_string2()
{
    std::string str("hello");
    return str;
}
  1. Будет ли применяться РВО во всех трех случаях?
  2. Можно ли вернуть временное значение, как в приведенном выше коде? Я считаю, что это нормально, поскольку я возвращаю его по значению, а не возвращаю какую-либо ссылку на него.

Есть предположения?


person Navaneeth K N    schedule 08.09.2009    source источник


Ответы (4)


В первых двух случаях будет иметь место оптимизация RVO. RVO — старая функция, и большинство компиляторов поддерживает ее. Последний случай — это так называемый NRVO (Named RVO). Это относительно новая функция C++. Стандарт позволяет, но не требует реализации NRVO (как и RVO), но некоторые компиляторы поддерживают его.

Вы можете прочитать больше о RVO в пункте 20 книги Скотта Мейерса Более эффективный С++. 35 новых способов улучшить ваши программы и проекты.

Вот хорошая статья о NRVO в визуальном С++ 2005.

person Kirill V. Lyadvinsky    schedule 08.09.2009
comment
Есть ли способ в VS2008 отключить все оптимизации, включая RVO? Я отключил все, но он все еще делает RVO. Это просто для понимания различий. - person Navaneeth K N; 08.09.2009
comment
/Od следует отключить NRVO, но я не уверен насчет RVO. - person Kirill V. Lyadvinsky; 08.09.2009
comment
Я почти уверен, что RVO не применяется к отладочной сборке. - person Alexandre Bell; 08.09.2009
comment
@Krugar: Ну, это применяется. Я запускаю отладочную сборку. - person Navaneeth K N; 08.09.2009
comment
MSVC применяет RVO, даже если оптимизация отключена. Однако это не относится к NRVO. - person jalf; 08.09.2009
comment
Как вы это тестируете? Создайте следующий код как в режиме отладки, так и в режиме выпуска: #include ‹iostream› class myClass { public: myClass() {} myClass(const myClass&) { std::cout ‹‹ RVO не применялся!\n; } }; myClass foo() { панель myClass; обратная планка; } int main() { myClass baz = foo(); } - person Alexandre Bell; 08.09.2009

Во-первых, совершенно нормально возвращать временное значение по значению, что вы и делаете. Он копируется, и хотя оригинал выйдет за пределы области действия, копия этого не сделает и может безопасно использоваться вызывающим.

Во-вторых, все три случая на самом деле идентичны (поскольку вы все равно не обращаетесь к временному объекту в третьем случае), и компилятор может даже выдать один и тот же код для всех из них. Следовательно, он может использовать RVO во всех трех случаях. Это полностью зависит от компилятора.

person sharptooth    schedule 08.09.2009

Все случаи правильные. Все они будут создавать временные объекты и применять конструктор копирования возвращаемого типа. Обязательно, если нет конструктора копирования, код не будет работать.

RVO произойдет во всех трех случаях в большинстве компиляторов. Единственная разница заключается в последнем, где стандарт не требует этого. Это потому, что у вас есть именованная переменная. Но большинство компиляторов достаточно умны, чтобы применить к ней RVO... чем позже объявлена ​​именованная переменная и чем меньше преобразований она применяется, тем больше шансов, что RVO будет применен к именованной переменной.

Между прочим, возвращение ссылки, конечно же, возможно, как вы могли видеть в другом коде. Чего вы не должны делать, так это возвращать ссылку на локальный объект.

std::string& get_a_string2()
{
    std::string str("hello");
    return str; //error!
}

Как вы знаете, вызовет ошибку времени компиляции. Тем не мение,

std::string& get_a_string2(std::string& str)
{
    // do something to str
    return str; //OK
}

Будет работать нормально. В этом случае нет конструкции или конструкции копирования. Просто функция возвращает ссылку на свой аргумент.

person Alexandre Bell    schedule 08.09.2009
comment
Однако я бы предпочел, чтобы эта функция была пустой и ничего не возвращала, поскольку она обрабатывает параметр. Возврат ссылочного параметра просто запутает других людей при просмотре кода. - person Viktor Sehr; 08.09.2009

  1. Это зависит от вашего компилятора - какую платформу вы имеете в виду? Лучший способ выяснить это — скомпилировать очень маленькое тестовое приложение и проверить ASM, который выдает ваш компилятор.

  2. Да, все в порядке, хотя вы никогда не говорите о том, что вас беспокоит; скорость? стиль? вы можете сделать локальную временную ссылку константной — время жизни временной ссылки будет продлено до времени жизни ссылки — попробуйте и убедитесь сами! (Херб Саттер объясняет это здесь) См., например, конец сообщения.

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

int foo() { return 42; }

int main(int, char**)
{
    const int &iRef = foo();
    // iRef is valid at this point too!
}
person Thomi    schedule 08.09.2009
comment
вы можете вернуть константную ссылку на локальное временное нет, это неправильно. Вы не можете вернуть ссылку на локальный или временный объект, созданный в этой функции. Они будут уничтожены после возврата из функции, а возвращаемое значение ссылки будет относиться к мусору. Я думаю, вы имели в виду const T& t = T();, а это другое дело. - person Johannes Schaub - litb; 08.09.2009
comment
Да, именно это я и имел в виду. Теперь я сделал это более явным. - person Thomi; 08.09.2009