不同于普通的双目运算符“+/-”,自增自减运算符“++/--”在重载的时候需要额外留意区分是前置式还是后置式。现行的处理方法是,(作为成员函数的)前置“++”不接受任何参数,而后置“++”接受一个int类型的参数,尽管没什么实际用途,但是却为编译器确定重载对象提供了帮助。
除了区分的时候需要注意,他们的行为也是有所区别的。援引More Effective C++中的话,前置++是increment and fetch,而后置++则是fetch and increment。所以我们在重载这些运算符时也要符合这些要求。
还有一点,则是需要特殊小心地处理返回值问题。前置++通常情况应该返回对象的引用,而后置式的++应该返回一个const修饰的对象实体。
前置++返回引用的理由很简单,比如对前置++拥有如下行为:
++(++a)
首先需要明确的是,运算符实际上也是一个函数调用,也是具有返回值的。那么第二次++是对第一次++的返回值进行操作。如果我们不返回一个引用而返回一个新建的临时对象,那么第二次++得到的便是对这个临时对象再进行自增操作的结果,从而导致这条语句对实际的对象a,只能够进行一次自增操作。可以查看其反汇编代码,能够证实这一点。而后置的++返回一个对象实体,则也很简单,因为你在后置++中,需要临时保存旧的值,而这个旧的值必须是后置++的函数体内部的临时变量。返回一个临时变量的指针或者引用是危险的、不允许的。至于为什么需要const修饰,则是要保证a++++这类表达式在编译时期非法而不是到运行时期出现不期望的结果。为什么不能出现a++++,一是内置的数据类型(比如int)不允许我们这样做;二是就算自定义的类能够实现这种行为,它也是不符合你的一般常识性想法的:我们通常认为两个++并在一起会导致a连续递增两次,而正如上面分析前置++连用,返回对象而不是引用,则让第二次的操作对象为一个临时对象而不是其本身,与我们预期不一致。为了避免这种误会,必须要用const修饰符来显式禁止。函数返回const对象能够确保不作用在非const的成员函数上,而operator++确实是非const成员函数。
另外,建议在重载后置++时使用++(*this),而前置++使用*this += 1。因为支持自增运算的对象必然支持加法运算,这样能够显著提高代码的简洁性和可读性。编译器的内联函数优化也并不会增加多少执行速度上的额外开销(相比于在++内部直接写函数体)。
这样我们就得出了通用的、重载++操作符的范例代码了:
1 //prefix ++ : increment and fetch 2 myclass & myclass::operator++() 3 { 4 *this += 1; 5 return *this; 6 } 7 //postfix ++ : fetch and increment 8 const myclass myclass::operator++(int) 9 {10 myclass tmp = *this;11 ++(*this);12 return tmp;13 }
关于效率上,通常有“前置式自增”效率较佳,因为前置运算符不需要产生临时对象和对临时对象进行构造函数和析构函数的调用。如果这个对象比较大,内存复制的时间也是较大的。因此,如果不是特殊需要,一般尽可能使用前置++于类类型。这也是似乎在运用STL迭代器时,尽可能在循环使用前置++的原因。
参考:More Effective C++,Meyer Scott,侯捷译,电子工业出版社