条款6:区别 increment/decrement 操作符的前置(prefix)和后置(postfix)形式
文章目录
区分前置式和后置式
众所周知,重载函数是以其参数类型来区分彼此的。
然而不论 increment
或 decrement
操作符的前置(prefix
)和后置(postfix
)形式,都没有参数。
为了填平这个漏洞,只好让后置式有1个int
自变量,并且在它被调用时,编译器默默地为该 int
指定0值:
class RandyInt {
public:
RandyInt& operator++(); // 前置式(prefix)++
const RandyInt operator++(int); // 后置式(postfix)++
RandyInt& operator--(); // 前置式(prefix)--
const RandyInt operator--(int); // 后置式(postfix)--
RandyInt& operator+=(int); // +=操作符,结合 RandyInt 和 ints
...
};
RandyInt i;
++i; // 调用 i.operator++()
i++; // 调用 i.operator++(0)
--i; // 调用 i.operator--()
i--; // 调用 i.operator--(0)
这里前置式返回一个引用(reference
),increment
的前置式意义是“increment and fetch
”(累加然后取出)
后置式返回一个const
对象,decrement
的后置式意义是"fetch and increment
"(取出然后累加)
实现
class RandyInt {
public:
RandyInt& operator++() {
*this += 1; // 累加(increment)
return *this; // 取出(fetch)
};
const RandyInt operator++(int) {
RandyInt oldValue = *this; // 取出(fetch)
++(*this); // 累加(increment)
return oldValue; // 返回先前取出的值
};
RandyInt& operator--() {
*this -= 1;
return *this;
};
const RandyInt operator--(int) {
RandyInt oldValue = *this;
--(*this);
return oldValue;
};
RandyInt& operator+=(int);
RandyInt& operator-=(int);
};
后置式操作符并未调用其入参,该入参唯一的作用就是区别前置式和后置式。
当我们故意省略入参的名称时,编译器就不会对未使用的入参发出警告。
为什么后置式 increment 操作符必须返回1个对象(代表旧值),还是个 const 对象呢?
返回1个对象,因为该对象存储的是累加之前的旧值。
必须是个const 对象的原因是防止多重累加,看下面的代码:
RandyInt i;
i++++; // wrong!实时后置式 increment 操作符2次
// 相当于下面的动作:
i.operator++(0).operator++(0);
operator++(0)
的第二个调用动作施行于第一个调用动作的返回对象,而该返回对象是个const,不允许2次累加。
如果返回值不是const
,则可以进行2次累加,但是第2次累加的作用对象是第1个operator++(0)返回的对象,而不是原对象,原对象只被累加了1次!这么做会在开发过程中与预期不符!
但是++++i
是允许的,改变的都是原对象。
效率比较
从实现能看出来, 后置式 increment
函数必须产生1个临时对象,用作返回值,该临时对象需要构造,也需要析构。
但从效率而言,前置式 不需要多创建1个对象,就能完成自身数值变化。
因此如果仅仅想改变某个RandyInt
的值,使用 ++i
的效率比 i++
高。
前置式和后置式保持行为一致性
无论前置式 increment
还是后置式 increment
操作符,它们都是将某个值累加。
因此为了确保后置式 increment 的行为与前置式 increment 行为一致,后置式 increment 和 decrement 操作符的实现应以其前置式兄弟为基础。
如此一来,只需要维护前置式版本就行了。