【问题标题】:Premature optimization or am I crazy?过早的优化还是我疯了?
【发布时间】:2011-03-27 07:48:46
【问题描述】:

我最近在 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() 的调用,但我只看到一个。这是什么鬼?是我疯了,还是我的调试器疯了,或者这是过早优化的结果?

【问题讨论】:

  • static int x 未初始化。
  • 不过,这与问题无关。
  • @Rambo:我想你在谈论这个话题:groups.google.com/group/comp.lang.c++.moderated/browse_thread/…
  • @Kirill:如果您不指定其他值,静态变量将被初始化为零。实际上,如果您想获得技术知识,无论如何它们都是零初始化的,并且只有在您指定之后才初始化为其他值(同一变量可以合法地初始化两次为两个不同值的罕见地方之一。
  • 就术语而言,“过早优化”必然是判断错误,并且仅适用于程序员所做的优化(试图优化对程序速度没有显着影响的东西,并使东西不可读或在此过程中更难维护)。编译器的优化对源代码没有影响,因此要么正确要么不正确(一个错误)。

标签: c++ reference premature-optimization


【解决方案1】:

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

但本质上,左侧只被评估一次。它会产生一个非常量的左值,这就是读取该值、加一个并将其写回所需的全部内容。

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

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

【讨论】:

    【解决方案2】:

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

    顺便说一句,vb.net 和 Pascal 中的“With”语句都具有相似的功能。像这样的声明:

    ' Assime Foo 是某种结构的数组,Bar 是一个函数,Boz 是一个变量。 与 Foo(酒吧(Boz)) .Fnord = 9 .嘎嘎= 10 结束于 将计算 Foo(Bar(Boz)) 的地址,然后将该结构的两个字段设置为值 9 和 10。它在 C 中相当于 { FOOTYPE *tmp = Foo(Bar(Boz)); tmp->Fnord = 9; tmp->嘎嘎= 10; }

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

    【讨论】:

    • 显然例如正如 James 在他的回答中指出的那样,C# 两次评估 LHS。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      这就是标准对+= 和朋友的评价:

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

      所以编译器是正确的。

      【讨论】:

      • 不错的发现。 _______________ :-)
      【解决方案5】:

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

      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;
      

      编译器不会进行两次函数调用,除非您明确地将两个函数调用放入代码中。如果你的函数有副作用(就像你做的那样)并且编译器开始添加额外的难以查看的隐式调用,那么实际上很难理解代码的流程,从而使维护变得非常困难。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-21
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-01
        • 2010-09-27
        • 1970-01-01
        相关资源
        最近更新 更多