Я недавно видел часть кода в 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.17-7: Поведение выражения формы E1 op = E2 эквивалентно E1 = E1 op E2, за исключением того, что E1 является оценивается только один раз. [...]
Так что компилятор прав в этом.
i+=1
- это функционально то же самое, что и i=i+1
. На самом деле он реализован по-другому (в основном, он предназначен для использования преимуществ оптимизации на уровне процессора).
Но по сути левая часть оценивается только один раз. Она дает неконстантное l-значение, и это все, что нужно, чтобы прочитать значение, добавить единицу и записать его обратно.
Это более очевидно, когда вы создаете перегруженный оператор для пользовательского типа. operator+=
изменяет экземпляр this
. operator+
возвращает новый экземпляр. Обычно рекомендуется (в C++) сначала написать oop+=, а затем написать op+ в его терминах.
(Обратите внимание, что это относится только к C++; в C#, op+=
- это именно то, что вы предполагали: просто сокращение для op+
, и вы не можете создать свой собственный op+=. Он автоматически создается для вас из op+)
Ваше мышление логично, но неверно.
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;
Компилятор не будет выполнять два вызова функций, если вы явно не поместите в код два вызова функций. Если у вас есть побочные эффекты в ваших функциях (как и у вас), и компилятор начал добавлять неявные вызовы, которые трудно увидеть, было бы очень трудно понять поток кода и, таким образом, очень усложнить обслуживание.
f()
возвращает ссылку на статическое целое число. Затем += 1
добавляет единицу к этой области памяти - нет необходимости вызывать его дважды в операторе A.
На каждом языке, который я видел, который поддерживает оператор + =, компилятор оценивает операнд в левой части один раз, чтобы получить некоторый тип адреса, который затем используется как для чтения старое значение и напишите новое. Оператор + = - это не просто синтаксический сахар; как вы заметили, он может достичь семантики выражения, чего было бы неудобно достичь другими способами.
Между прочим, операторы «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 и Паскаль не предоставляют временный указатель. В то время как можно добиться того же эффекта в VB.net без использования «With» для хранения результата Bar (), использование «With» позволяет избежать использования временной переменной.