过早优化还是我疯了?

时间:2010-08-09 13:59:59

标签: c++ reference premature-optimization

我最近在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 个答案:

答案 0 :(得分:27)

这就是标准所说的关于+=和朋友的信息:

  

5.17-7:E1 op = E2形式的表达式的行为等同于   E1 = E1操作E2除了E1是   只评估过一次。[...]

所以编译器就是这样。

答案 1 :(得分:10)

i+=1 功能i=i+1相同。它实际上是以不同的方式实现的(基本上,它旨在利用CPU级别优化)。

但实际上,左侧只评估一次。它产生一个非const l值,只需要读取值,添加一个并将其写回。

为自定义类型创建重载运算符时,这一点更为明显。 operator+=修改this实例。 operator+返回一个新实例。通常建议(在C ++中)首先编写oop + =,然后根据它编写op +。

(注意这仅适用于C ++;在C#中,op+=完全按照您的假设:只是op+的简写,而您无法创建自己的op + =。它会自动创建为了你的Op +)

答案 2 :(得分:9)

你的想法是合乎逻辑的但不正确。

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 :(得分:3)

f()返回对静态整数的引用。然后+= 1在此内存位置添加一个 - 不需要在语句A中调用它两次。

答案 4 :(得分:0)

在我看过的每种支持+ =运算符的语言中,编译器会对左侧的操作数进行一次计算,以产生某种类型的地址,然后将其用于读取旧值并编写新值一。 + =运算符不仅仅是语法糖;正如您所注意到的,它可以实现表达语义,这通过其他方式很难实现。

顺便说一句,vb.net和Pascal中的“With”语句都有类似的功能。如下声明:

' 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))的地址,然后将该结构的两个字段设置为值9和10。它在C到

中是等价的
  {
    FOOTYPE *tmp = Foo(Bar(Boz));
    tmp->Fnord = 9;
    tmp->Quack = 10;
  }

但是vb.net和Pascal不公开临时指针。虽然可以在VB.net中实现相同的效果而不使用“With”来保存Bar()的结果,但使用“With”可以避免临时变量。