Explicit——谨慎定义隐式类型转换函数
在查找别的资料的时候,看到这么一个关键字,以前都没见过觉得挺有用,
于是找来More Effective C++进行学习总结一下。
一 隐式转换
C++编译器能够在两种数据类型之间进行隐式转换(implicit conversions),它继承了C语言的转换方法。
隐式把char——>int和从short——>double。转换可能会导致数据的丢失。
自定义的类型时,你就可以有更多的控制力,因为你能选择是否提供函数让编译器进行隐式类型转换。
有两种函数允许编译器进行这些的转换:单参数构造函数(single-argument constructors)和隐式类型转换运算符。
二 隐藏的隐式转换
1 单参数构造函数是指只用一个参数即可以调用的构造函数。
该函数可以是只定义了一个参数,也可以是虽定义了多个参数但第一个参数以后的所有参数都有缺省值。
什么什么情况会发生隐式类型转换,和构造函数参数类型有关。
Example1: class Name { // for names of things public: Name(const string& s); // 转换 string 到 { // Name ... }; char ch = 'a'; string str; Name name(str); name = str; //Right str会隐式转换为Name类型,会执行其构造函数 name = ch; //Error Name构造函数参数类型为string 而不是char
Example2: class Rational { // 有理数类 public: Rational(int numerator = 0, // 转换int到 int denominator = 1) // 有理数类Rational { } Rational(Name name) // 转换Name类型到 有理数类Rational { } Rational(string str) // 转换string类型 到有理数类 { } }; char ch = 'a'; string str; Name name(str); int i = 3; Rational ra;
//以下均Right ra = i; // 执行Rational(int numerator = 0,int denominator = 1)转换 ra = ch; //执行Rational(int numerator = 0,int denominator = 1)转换 ra = str; //执行Rational(string str)转转 ra = name; //执行Rational(Name name)转换
从以上两个例子可以看到:在单参数构造函数中隐式转换很容易发生。
2 隐式类型转换运算符
就是 operator 关键字,其后跟一个类型符号。
class Rational { // 有理数类 public: …… operator double() const // 转换Rational类成 double类型 { }
operator string () const // 转换Rational类成 string类型 { } }; str = ra; // 执行operator string ()转换string 运行崩溃 i = 1*ra; //执行operator double()转换成double型 cout<<ra; //执行operator double() 转换成double型输出随机浮点数
所以避开类型转换运算符!
三 隐式转化的消除
1 隐式类型转换运算符
不提供这种operator转换运算符。使用等同的函数来替代转换运算符;
用asDouble函数代替operator double函数。
double asDouble() const; //转变 Rational 成double cout << ra.asDouble(); //相当于提供一个接口
2 单参数构造函数进行隐式类型转换消除
template<class T> class Array { public: Array(int lowBound, int highBound){} //不能做隐式类型转换 Array(int size) //可以做隐式类型转换 { m_size = size; m_array = new T(size); } T& operator[](int index) { return m_array[index]; } bool operator==( const Array<int>& rhs) { //成员重载函数只能单参数 }
friend bool operator==(const Array<int>& lhs,const Array<int>& rhs) { //这个函数是有问题的,比较的应该是数值,但却成了Array对象比较 if (lhs == rhs) //又会调用friend bool operator 死循环 { return 1; } return 0; } private: int m_size; T* m_array; };
Array<int> a(10); Array<int> b(10); for (int index = 0; index < 10; ++index) { if (a == b[index]) //执行friend bool operator 崩溃 { // 哎呦! "a" 应该是 "a[i]" //do something for when //a[i] and b[i] are equal; } else { //do something for when they're not; } } //实际中 for (int i = 0; i < 10; ++i) if (a == static_cast< Array<int> >(b[i])) //每次比较都会转换
一不小心就会,碰上这种隐式转换问题,且不容易发觉问题所在。
解决这个问题:
一是使用关键字explicit (这个需要编译器支持!)
编译器会拒绝为了隐式类型转换而调用构造函数。显式类型转换依然合法。
class Name { // for names of things public: explicit Name(const string &s) // 转换 string 到 Name { } };
string str; Name name(str); name = static_cast<Name> (str); //Right 显式转换 name = str; //Error explicit禁止隐式类型转换
二是 更改构造函数参数
因为类中隐式类型转换仅限于单参数构造函数中。且和构造函数的参数类型有关。
所以可以对构造函数的参数类型进行修改。通常没有一个转换能够包含用户自定义类型。
对于刚才的例子class Array中构造函数Array(int size)是可以做隐式类型转换
对参数size包装成,我们自定义类型。
将size交给内置的ArraySize类来管理。
template<class T> class Array { public: class ArraySize { // 这个类是新的 public: ArraySize(int numElements): theSize(numElements) {} int size() const { return theSize; } private: int theSize; }; Array(int lowBound, int highBound); Array(ArraySize size); // 注意新的声明 ... };
|