Преждевременная оптимизация или я сумасшедший? - PullRequest
7 голосов
/ 09 августа 2010

Недавно я увидел фрагмент кода на comp.lang.c ++, который модерировал возвращение ссылки на статическое целое число из функции. Код был примерно такой

int& f()
{
   static int x;
   x++;
   return x;
}

int main()
{
  f()+=1; //A
  f()=f()+1; //B
  std::cout<<f();

}

Когда я отлаживал приложение с помощью моего классного отладчика Visual Studio, я видел только один вызов оператора A и догадывался, что меня шокировало. Я всегда думал, что i+=1 было равно i=i+1, поэтому f()+=1 будет равно f()=f()+1, и я буду видеть два вызова на f(), но я видел только один. Что, черт возьми это? Я сумасшедший или мой отладчик сошел с ума или это результат преждевременной оптимизации?

Ответы [ 5 ]

27 голосов
/ 09 августа 2010

Вот что говорит Стандарт о += и друзьях:

5.17-7: поведение выражения вида E1 op = E2 эквивалентно E1 = E1 op E2 за исключением того, что E1 оценивается только один раз. [...]

Так что компилятор в этом прав.

10 голосов
/ 09 августа 2010

i+=1 является функционально так же, как i=i+1.На самом деле он реализован по-другому (в основном, он предназначен для использования преимуществ оптимизации уровня процессора).

Но, по сути, левая сторона оценивается только один раз.Он выдает неконстантное l-значение, которое является всем, что нужно для чтения значения, добавления его и записи обратно.

Это становится более очевидным, когда вы создаете перегруженный оператор для пользовательского типа.operator+= изменяет экземпляр this.operator+ возвращает новый экземпляр.Обычно рекомендуется (в C ++) сначала написать oop + =, а затем написать op + в терминах этого.

(Обратите внимание, что это применимо только к C ++; в C # op+= точно так, как вы и предполагали: просто короткая рука для op+, и вы не можете создать свой собственный op + =. Он создается автоматически дляты из оп +)

9 голосов
/ 09 августа 2010

Ваше мышление логично, но не правильно.

i += 1;
// This is logically equivalent to:
i = i + 1;

Но логически эквивалентные и идентичные не совпадают.
Код должен выглядеть так:

int& x = f();
x += x;
// Now you can use logical equivalence.
int& x= f();
x = x + 1;

Компилятор не сделает два вызова функции, если вы явно не поместите два вызова функции в код.Если у вас есть побочные эффекты в ваших функциях (как и у вас), и компилятор начал добавлять очень трудно увидеть неявные вызовы, было бы очень трудно реально понять поток кода и, таким образом, сделать обслуживание очень трудным.

3 голосов
/ 09 августа 2010

f() возвращает ссылку на статическое целое число. Затем += 1 добавляет единицу в эту область памяти & ndash; нет необходимости вызывать его дважды в утверждении А.

0 голосов
/ 09 августа 2010

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

Кстати, операторы «With» в vb.net и Pascal оба имеют похожую функцию.Оператор наподобие:

' Assime Foo is an array of some type of structure, Bar is a function, and Boz is a variable.
  With Foo(Bar(Boz))
    .Fnord = 9
    .Quack = 10
  End With
вычислит адрес Foo (Bar (Boz)), а затем установит для двух полей этой структуры значения девять и десять.В C это будет эквивалентно
  {
    FOOTYPE *tmp = Foo(Bar(Boz));
    tmp->Fnord = 9;
    tmp->Quack = 10;
  }

, но vb.net и Pascal не предоставляют временный указатель.Хотя можно добиться того же эффекта в VB.net без использования «С» для хранения результата Bar (), использование «С» позволяет избежать временной переменной.

...