第二章,操作符

条款6 区别increment与decrement操作符的前置和后置形式

描述

重载操作符++和- -有前置式和后置式,主要通过后置式在参数中写有int自变量来做区分

class UPInt{
public:
UPInt& operator++(); //前置 ++
const UPInt operator++(int);//后置 ++

UPInt& operator--(); //前置 --
const UPInt operator--(int); //后置 --

UPInt& operator+=(int);// +=操作符,结合UPInts和ints
};

UPInt i;
++i; //i.operator++();
i++; //i.operator++(0);

--i; //i.operator--();
i--; //i.operator--(0);

前置式与后置式重载的实现

前置式的意义:increment and fetch 累加然后取出; 后置式的意义: fetch and increment 取出然后累加;这正式increment前置和后置操作符重载的实现规范。

//前置式:累加然后取出(increment and fetch)
UPInt& UPInt::operator++()
{
*this += 1; //累加
return *this; //取出
}

//后置式: 取出然后累加
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this; //取出
++(*this); //累加
return oldValue;
}

注意,这里的后置重载并没有动用函数参数,其参数的唯一目的只是区别前置式与后置式。

为什么后置式返回const对象?

UPInt i;
i++++; //实施“后置式increment 操作符”;两次
//同下
i.operator++(0).operator++(0);

由以上代码就可以知道为什么要返回const对象: operator++ 的第二个调用动作施行于第一个调用动作返回的对象上,如果不是const,那么这个++++的操作就被允许了,但这和内建类型(如: int)的行为不一致,例如,int类型是不允许出现以下操作的

int i;
i++++; //错误!

第二个理由是即使允许了i++++这种连续调用的操作,第二个operator++所改变的对象实际是第一个operator++操作返回的对象,实际原来的对象也只是进行了一次累加。因此这种写法是没有意义的,不应该被允许。

关于后置式的效率问题

//后置式: 取出然后累加
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this; //取出
++(*this); //累加
return oldValue;
}

上面代码可以看到,后置式重载的实现会产生一个临时对象 oldValue,需要构造也需要析构,这不免让人担忧其效率; 因此在处理用户的定制类型时应该尽量使用前置式。

如何确保后置式与前置式的行为一致?

提出这个问题,是因为前置式与后置式是两个独立的重载函数,设计者可能会不小心,将其中一个修改,那么重载便失去了意义,因为必须保证前置式与后置式他们做的就是累加或者递减操作,而不是前置式是一种行为,而后置式是另外一种行为。例如一种可能,前置式increment是累加类型A,而后置式却是累加类型B

为了确保前置式与后置式行为一致,后置式操作符应该以前置式操作符为基础,这样,即使有一天需要修改操作行为,也只需维护前置式就可以了。

//前置式:累加然后取出(increment and fetch)
UPInt& UPInt::operator++()
{
*this += 1; //累加
return *this; //取出
}

//后置式: 取出然后累加
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this; //取出
++(*this); //基于前置式操作,正确!

(*this)+=1; //错误! 很不利于维护

return oldValue;
}

关键要点

(1)知道前置式与后置式应该返回什么类型与其原因
(2)后置式基于前置式为实现基础,确保行为一致