Является ли RVO (оптимизация возвращаемого значения) для безымянных объектов универсально гарантированным поведением?

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

Однако этот термин разрешено сбивает с толку. Означает ли это, что RVO гарантировано произойдет на каждом компиляторе. Из-за изменения кода RVO ниже его наблюдаемое поведение:

#include<iostream>
int global = 0;
struct A {
  A(int *p) {}
  A(const A &obj) { ++ global; }
};

A foo () {  return A(0); }  // <--- RVO happens
int main () {
  A obj = foo(); 
  std::cout<<"global = "<<global<<"\n"; // prints 0 instead of 2
}

Предполагается ли, что эта программа печатает global = 0 для всех реализаций, независимо от оптимизации компилятора и размера метода foo?


person iammilind    schedule 07.12.2011    source источник
comment
С каких пор разрешено означает гарантировано?   -  person Lightness Races in Orbit    schedule 07.12.2011
comment
Мне очень жаль, что у меня нет никакого контроля над RVO. Такой вид портит все дело. В некоторых случаях мне бы хотелось, чтобы ошибка компиляции вместо RVO просто молча не происходила.   -  person Roman L    schedule 07.12.2011
comment
@ Томалак, вот в чем вопрос. allowed несколько запутанный термин. Это означает guaranteed или нет?   -  person iammilind    schedule 08.12.2011
comment
@iammilind: Нет; зачем это? Два английских слова имеют совершенно разные значения.   -  person Lightness Races in Orbit    schedule 08.12.2011
comment
Начиная с C++17, гарантируется исключение копии RVO безымянных объектов.   -  person ShadowRanger    schedule 05.01.2018


Ответы (3)


В соответствии со стандартом программа может печатать 0, 1 или 2. Конкретный абзац в C++11 — это 12.8p31, который начинается с:

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

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

Примечание 2: 1 не упоминается ни в одном из ответов, но это возможный результат. Имеются две возможные копии: от локальной переменной в функции до возвращаемого объекта и объекта в main, компилятор не может исключить ни одну, одну или две копии, генерирующие все три возможных вывода.

person David Rodríguez - dribeas    schedule 07.12.2011

Это не может быть гарантировано. Если бы вы попытались написать такую ​​гарантию связно, вы бы обнаружили, что это невозможно.

Например, рассмотрим этот код:

std::string f() {
  std::string first("first");
  std::string second("second");
  return FunctionThatIsAlwaysFalse() ? first : second;
}

Функция FunctionThatIsAlwaysFalse всегда возвращает false, но это можно сказать только в том случае, если вы выполняете межмодульную оптимизацию. Должен ли стандарт требовать, чтобы каждый отдельный компилятор выполнял межмодульную оптимизацию, чтобы в этом случае он мог использовать RVO? Как это сработает? Или он должен запретить любому компилятору использовать RVO, когда необходимы межмодульные оптимизации? Как это сработает? Как это может помешать компиляторам, которые достаточно умны, чтобы увидеть, что RVO возможен, сделать это, а тем, кто этого не делает, не сделать этого?

Должен ли стандартный список поддерживать все компиляторы оптимизации с RVO? И должен ли он запрещать РВО в других случаях? Разве это не помешает оптимизации компиляторов?

А как насчет случаев, когда компилятор считает, что RVO снизит производительность? Должен ли компилятор выполнять оптимизацию, которую он считает плохой? Например:

if(FunctionCompilerKnowsHasNoSideEffectsAndThinksMostlyReturnsFalse())
  return F(3); // Here RVO is a pessimization
else
{
 Foo j=F(3);
 return Foo(j);
}

Здесь, если компилятору не требуется делать RTO, можно избежать if и вызова функции, так как без RTO код в обеих половинах одинаков. Следует ли заставлять компилятор выполнять оптимизацию, которая, по его мнению, ухудшает ситуацию? Почему?

На самом деле нет никакого способа заставить такую ​​гарантию работать.

person David Schwartz    schedule 07.12.2011
comment
Ваш ответ по-своему хорош, однако я прямо упомянул связь RVO с безымянными объектами. - person iammilind; 07.12.2011
comment
Это был просто пример того, почему RVO не может быть гарантирована. Принцип тот же - вам нужно указать один и тот же набор оптимизаций для каждого компилятора, что лишает смысла разрешать оптимизацию. (Рассмотрите случай, когда RVO может фактически снизить производительность, например, когда RVO является ветвью, которая, по прогнозам компилятора, почти никогда не будет выбрана.) - person David Schwartz; 07.12.2011
comment
@DavidSchwartz: Хотя это пример, в вашем конкретном случае, если вы замените переменные конструкциями на месте (например, return condition() ? std::string("first") : std::string("second");), все известные мне компиляторы избегают копирования. Тот факт, что в вопросе упоминается unnamed, имеет большое значение, так как это означает, что компилятор знает, что создаваемый объект будет возвращен (поскольку конструкция находится в оператор возврата). Как и при любой оптимизации, это не гарантируется, но все компиляторы делают. - person David Rodríguez - dribeas; 07.12.2011
comment
Почему вы думаете, что в последнем примере РВО будет песимизацией?? Независимо от того, существует ли if в окончательном скомпилированном коде или нет, компилятор может выполнить NRVO для второй ветви и полностью удалить первую ветвь. Даже если он не удалит его, он может преобразовать код в: Foo j = F(3); return j; в обоих случаях и в любом случае исключить две копии. Потрудитесь объяснить, почему вы думаете, что он не сможет этого сделать? - person David Rodríguez - dribeas; 07.12.2011
comment
Возможно, это не лучший пример. Но дело в том, что если у вас есть «если», где один случай может быть RVO, а другой случай требует именно того, что компилятор сделал бы, если нет RVO, «если» может быть дороже, чем преимущества RVO. . Так что RVO не всегда является оптимизацией. Спецификация должна либо сказать, что компилятор должен сделать это, даже если это не оптимизация, либо она должна потребовать, чтобы программист знал, когда RVO является или не является оптимизацией на конкретной платформе. Опять же, это просто пример. Просто невозможно назначить RVO без всяких глупостей. - person David Schwartz; 07.12.2011

Педантично говоря, его реализация определена. Современные компиляторы достаточно умны, чтобы проводить такую ​​оптимизацию.

Но нет гарантии, что поведение будет одинаковым во всех реализациях. Вот что такое поведение, определяемое реализацией.

«разрешено» в этом контексте означает, что 0 или 1 или 2 являются выходными данными, соответствующими стандарту.

person Prasoon Saurav    schedule 07.12.2011
comment
IMO только 0 должно быть наблюдаемым поведением. Сомнительно, что стандарт может разрешить оба выхода. - person iammilind; 07.12.2011
comment
Почему нет? Стандарт также позволяет различать размеры примитивных типов в разных реализациях. Например, sizeof(int) может выводить 2 или 4 в зависимости от компилятора, верно? Есть и несколько других примеров. - person Prasoon Saurav; 07.12.2011
comment
@iammilind: стандарт явно разрешает 0, 1 и 2 в качестве вывода этой программы. Это не является частью правила как если бы для оптимизации, поскольку язык явно требует, чтобы программы не зависели от побочных эффектов конструктора копирования, и позволяет компиляторам свободно опускать копии. - person David Rodríguez - dribeas; 07.12.2011
comment
@Prasoon Saurav: Педантично говоря, его реализация определила Нет. Педантично говоря, это unspecified. Определенная реализация означает, что документация по реализации компилятора должна явно определять ее, что неверно. - person Serge Dundich; 07.12.2011