条款5:对定制的“类型转换函数”保持警觉
1、两种函数允许编译器执行隐式转换:单自变量constructor 和隐式类型转换操作符。单自变量constructor是指能够以单一自变量成功调用的constructor,这样的constructor可能声明拥有单一参数,也可能拥有多个参数,并且除了第一参数之外都有默认值。隐式类型转换操作符,是一个拥有奇怪名称的member function:关键词operator之后加上一个类型名称。不能为此函数指定返回值类型,因为其返回值类型基本上已经表现于函数名称上了。
2、最好不要提供任何类型转换函数,因为在你从未打算也未预期的情况下,此类函数可能会被调用,而其结果可能是不正确、不直观的程序行为,很难调试。下面用例子来说明:
一、隐式类型转换操作符例子:
#include<iostream> using namespace std; //参见http://blog.csdn.net/ruan875417/article/details/47260827条款24的例子 class Rational{ public: Rational(int n = 0, int d = 1) :numerator(n), denominator(d){}//可以把int转换为Rational int getNumerator() const{ return numerator; } int getDenominator() const{ return denominator; } operator double() const{ //隐式类型转换操作符,将Rational转换为double return static_cast<double>(numerator) / denominator; } /*friend ostream& operator<<(ostream& os, Rational& r){ os << r.numerator << '/' << r.denominator; return os; }*/ private: int numerator; int denominator; }; int main(){ Rational oneHalf(1, 2); cout << oneHalf << endl; system("pause"); return 0; }
上述例子中不注释掉friend ostream&operator<<(ostream& os, Rational& r)函数的运行结果:
上述例子中注释掉friend ostream&operator<<(ostream& os, Rational& r)函数的运行结果:
上述例子中如果Rational类存在friendostream& operator<<(ostream& os, Rational& r)函数,cout << oneHalf << endl;语句就会调用该函数,但当不存在friend ostream&operator<<(ostream& os, Rational& r)函数时,Rational类存在operator double() const这个隐式类型转换操作符,它就将oneHalf隐式转换为double,cout <<oneHalf << endl;调用也能成功。这说明了隐式类型转换操作符的缺点:可能导致错误(非预期)的函数被调用。
解决方法:以功能对等的另一个函数取代类型转换操作符。
#include<iostream> using namespace std; //参见http://blog.csdn.net/ruan875417/article/details/47260827条款24的例子 class Rational{ public: Rational(int n = 0, int d = 1) :numerator(n), denominator(d){}//可以把int转换为Rational int getNumerator() const{ return numerator; } int getDenominator() const{ return denominator; } double asdouble() const{ //将Rational转换为double return static_cast<double>(numerator) / denominator; } private: int numerator; int denominator; }; int main(){ Rational oneHalf(1, 2); //cout << oneHalf << endl;//错误,Rational没有operator<< cout << oneHalf.asdouble() << endl;//正确 system("pause"); return 0; }
二、单自变量constructors完成的隐式转换例子:
template<class T> class Array{ public: Array(int lowBound, int highBound); Array(int size); T& operator[](int index); ... }; bool operator == (const Array<int>& lhs, const Array<int>& rhs); Array<int> a(10); Array<int> b(10); .... for (int i = 0; i<10; ++i) if (a == b[i])//此处a应该是a[i]才对 do something; else do something else;
上述例子中对于if (a == b[i])语句,希望编译器报错,但却能顺利编译通过,因为b[i]可以通过Array(int size)函数转换为Array<int> object,会产生类似以下代码:
for (int i = 0; i<10; ++i) if (a == static_cast< Array<int> >(b[i]);
此行为还非常没效率,因为每次走过这个循环,必须产生和销毁一个临时的Array<int> objec。
解决方法:
a、用关键字explicit。只要将constructors声明为explicit,编译器便不能因隐式类型转换的需要而调用它们。
explicit Array(int size);//注意,使用explicit
b、C++中有一条规则:没有任何一个转换程序可以内含一个以上的“用户定制转换行为”。即编译器不能两次调用隐式类型转换函数。所以可以这样做:
template<class T> class Array{ public: class ArraySize{ public: AraaySize(int numElements) : theSize(numElements) {} int size() const { return theSzie; } private: int theSize; }; Array(int lowBound, int highBound); Array(ArraySize size);//注意这个新声明 ... };
上述例子仍然可以用一个int自变量构造起一个Array对象,还能阻止你希望避免的类型转换动作。类似ArraySize这样的classes,往往被称为proxy classes,因为它的每一个对象都是为了其他对象而存在的,好像其他对象的代理人一般。
条款6:区别increment/decrement操作符的前置(prefix)和后置(postfix)形式
1、C++中允许++和--操作符的两种形式(前置式和后置式)拥有重载能力。重载函数是以参数类型来区分的,然而不论是++还是--操作符的前置式或后置式都没有参数,为了区分这两种不同的操作,只好让后置式有一个int自变量,并且在它被调用时,编译器默认给该int指定一个0值。
例子:
#include<iostream> using namespace std; class UPInt{ //unlimited precision int public: UPInt(int i) :value(i){} UPInt(UPInt& upint) :value(upint.value){} UPInt& operator++(); //前置式 const UPInt operator++(int); //后置式 UPInt& operator--(); //前置式 const UPInt operator--(int);//后置式 friend ostream& operator<<(ostream& os,const UPInt& upint); private: int value; }; //前置式:累加后取出 UPInt& UPInt::operator++(){//返回一个引用 this->value += 1; return *this; } //后置式:取出后累加 const UPInt UPInt::operator++(int){//返回一个const对象 UPInt oldValue = *this; ++(*this);//后置式increment和decrement 操作符的实现应以其前置式兄弟为基础。 return oldValue; } //前置式:减后取出 UPInt& UPInt::operator--(){//返回一个引用 this->value -= 1; return *this; } //后置式:取出后减 const UPInt UPInt::operator--(int){//返回一个const对象 UPInt oldValue = *this; --(*this);//后置式increment和decrement 操作符的实现应以其前置式兄弟为基础。 return oldValue; } ostream& operator<<(ostream& os,const UPInt& upint){ os << upint.value; return os; } int main(){ UPInt i = 5; cout << i++ << endl;//5 cout << ++i << endl;//7 cout << i-- << endl;//7 cout << --i << endl;//5 system("pause"); return 0; }
2、后置式操作符并未动用其参数,其参数的唯一目的是为了区别前置式和后置式而已。
3、后置式操作符必须返回一个const对象的原因是如果不是const对象,以下调用将合法:
UPInt i = 5; i++++;
即调用i.operator++(0).operator++(0);
不欢迎上述调用的理由有两个:a、它和内建类型的行为不一致;b、其行为非你所预期,第二个operator++所改变的对象是第一个operator++返回的对象,而不是原对象。i只是被累加一次,违反直觉。
4、相较于前置式操作符,后置式操作符效率会更低,因为后置式需要产生一个临时对象来存储原对象的值,然后再返回原对象的值,会发生构造和析构。所以处理用户定制类型时,应该尽可能使用前置式操作符。
5、确定后置式操作行为和前置式的行为一致的原则是后置式increment和decrement 操作符的实现应以其前置式兄弟为基础。
版权声明:本文为博主原创文章,未经博主允许不得转载。