Преждевременная оптимизация или я сумасшедший?
Недавно я увидел фрагмент кода на 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 ответов
Об этом говорит Стандарт +=
и друзья:
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+)
Ваше мышление логично, но не правильно.
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
добавляет один в эту ячейку памяти - нет необходимости вызывать его дважды в операторе А.
На каждом языке, который я видел, который поддерживает оператор +=, компилятор вычисляет операнд с левой стороны один раз, чтобы получить некоторый тип адреса, который затем используется как для чтения старого значения, так и для записи нового. Оператор += - это не просто синтаксический сахар; как вы заметили, он может достичь семантики выражений, чего было бы неудобно достигать другими способами.
Кстати, операторы "With" в vb.net и Pascal имеют одинаковую функцию. Заявление как:
"Assime Foo - это массив некоторого типа структуры, Bar - это функция, а Boz - переменная. С Фу (Бар (Боз)) .Fnord = 9 .Quack = 10 Конец свычислит адрес Foo(Bar(Boz)), а затем установит для двух полей этой структуры значения девять и десять. Это было бы эквивалентно в C
{ FOOTYPE *tmp = Foo(Bar(Boz)); tmp->Fnord = 9; tmp-> Кряк = 10; }
но vb.net и Pascal не выставляют временный указатель. Хотя можно добиться того же эффекта в VB.net, не используя "С" для хранения результата Bar(), использование "С" позволяет избежать временной переменной.