C++中“&&”和“||”的重载问题

  之前一直认为C++中“&&”和“||”是不能被重载的。这种看法来源于几个事实。

  首先看小段代码:

#include <iostream>

int func(int i, int j) { return i + j; }

int main()
{
    int a = 1;
    int b = 0;
    b = func(a*=2, a+=1);
    std::cout << b << std::endl;
    return 0;
}

  这段代码会打印结果是不确定的。用一个确切的编译器编译后执行,会得到一个确切的结果。但是如果换一个编译器,结果可能又会不一样。为什么?

  这里涉及到一个序列点(sequence point)的概念。序列点可以认为是表达式求值的一个点,在这个点执行的时候所有带有副作用(side effects)的计算必须已经结束,所有下一个序列点带有副作用的计算还没开始。关于序列点的完整信息可以参考:http://en.wikipedia.org/wiki/Sequence_point

  函数调用在进入函数体之前存在一个序列点,它规定了在这个点之前函数的参数必须已经全部求值,但没有规定参数的求值顺序。

  之所以不限定参数求值顺序,个人的看法是为了给编译器厂商留下优化的空间,让编译器可以根据参数情况优化求值顺序。而不管编译器怎么优化,程序员只要保证各个参数的求值不会互相影响,就能得到一个确定的结果。

  回到我们的代码func(a*=2,a+=1):

  1. 正如上面所说,函数调用的序列点只要求调用func之前a*=2和a+=1已经计算完成,但并没有规定它们的计算顺序。
  2. 对于默认的__cdecl函数调用,虽然参数入栈顺序是从右到左,但这并不影响各个参数自身的求值顺序。
  3. 因此a*=2和a+=1哪个先计算是不确定的,不能编译器会有不同的表现。
  4. 如果a*=2先算,那么结果是6,如果a+=1先算,那么结果是8。

  程序的执行结果也证明了这一点:VS2010下运行结果是8,VC6.0下运行结果是6。

  来看 “&&”和“||”,在这两个符合在求值时也有一个序列点规定:它们的左边必须先求值完成,才能根据情况继续求值。比如p!=0 && p->Done(),在执行p->Done()之前p!=0必须已经执行完。同时“&&”还保证如果左边求值为false,整个表达式就会为false,右边的表达式就不会被求值。而“||”则保证如果左边求值为true,整个表达式就会为true,右边的表达式就不会被求值。即在满足特定情况下,整个表达式的一部分会被忽略,这就好像电线短路了一样,所以这种情况也叫短路求值,很贴切。大量的代码的正确性都是依赖于“&&”、“||”的序列点规定和短路求值特征。

  假设我们重载了“&&”,当我们写下p!=0 && p->Done()时,它实际上执行的是operator&&(p!=0, p->Done())。也就是重载之后“&&”就退化成函数调用了,这时候它遵循的是函数调用的序列点规则。也就是说这时候,p!=0和p->Done()不仅求值顺序变得不确定了,而且它们总是会被求值!那么到p真的为0时,p->Done()也会被执行,一般不会有什么好结果。

  这大概就是我多年前的想法,当时就武断地认为“&&”、“||”不能被重载,并一直保留这个看法,直到昨天恰巧查找了C++中不可被重载的符号列表,发现里面并没有“&&”、“||”。同时自己写程序试了一下,也确定可以重载。而我也应该反思为什么这么久才发现这个问题。

附:C++中不可被重载的函数列表

   Operator Name                           Syntax
Bind pointer to member by reference    a.*b
Member                                      a.b
Scope resolution                             a::b
Size of                                              sizeof(a)
Ternary                                             a ? b : c
Type identification                              typeid(a)

《The Design and Evolution of C++》里面有讲到“.”不可被重载的原因,讲得非常好,有兴趣的可以看看。

posted @ 2012-06-29 21:05  carter2000  阅读(1825)  评论(3编辑  收藏  举报