前言
除了c++提供的四种强制类型转换操作外,自定义类型在建立时我们可以提供某些函数以供编译器拿来作隐式转换。有两种函数允许编译器执行这样的转换:单自变量构造函数与隐式类型转换操作符。
隐式类型转换函数
单自变量构造函数
所谓单自变量构造函数,指的是能够以单一自变量成功调用的构造函数。该构造函数分为两种情况:
- 拥有单一形参
- 具备多个形参,但第一个形参之后的所有形参均具备默认值
1 | class Name { |
隐式类型转换操作符
隐式类型转换操作,是一个拥有奇怪名字的成员函数:operator关键词之后加一个类型名称。我们无法指定其返回值类型,因为其返回类型就体现在名称上。举例如下:1
2
3
4
5class Rational {
public:
...
operator double() const;
};
该函数会在以下情况被自动调用:1
2Rational r(1,2);
double d=0.5*r;//转换r,从Rational至double
自定义类型转换函数的弊端
隐式转换可能会在你并未打算调用他们的时候被调用,从而产生不正确的后果,我们很难调试中发现这一问题。
隐式类型转换操作符
问题实例
假设存在一个如上所述的 Rational类,我们希望他能够像内置类型一样兼容opertaor<<。但由于我们的疏忽,忘记了为Ration对象定义opertaor<<,你会惊奇地发现,如下语句还是能够编译和运行:1
2Rational r(1, 2);
cout << r;//本意是打印“1/2”,现在打印出了“0.5”
这揭示了隐式转换符的缺点:它们可能会导致非预期的函数被调用。
解决方案
解决方法很简单,以一个功能对等的函数取代它,例如:1
2
3
4
5class Rational {
public:
...
double asDouble() const;
};
在多数情况下,这种显式转换函数的使用不够方便,但是至少我们保证了一切按照预期运行,付出一些代价也是值得的。
单自变量构造函数
通过单自变量constructors完成的隐式转换难于消除,且其破坏性也较大。
问题实例
现有一个针对数组结构写的class template。这些数组允许用户指定索引值的上界与下界:1
2
3
4
5
6
7
8template<class T>
class Array {
public:
Array(int lowBound, int highBound);
Array(int size);//单自变量构造函数
T& operator[](int index);
...
};
以下代码试图比较Array<int>对象:1
2
3
4
5
6
7
8
9
10
11bool 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[i]写作a
...
}
else {
...
}
编译器对此并不会报错,事实上,它调用构造函数,将b[i]转为了一个Array<int>对象:1
2for (int i = 0; i < 10; ++i)
if (a == static_cast< Array<int> >(b[i]))
解决方案
我们不能禁止单参数构造函数,但同时我们也希望防止编译器不加鉴别地调用这个构造函数,因此我们可以使用explicit关键词。构造函数用 explicit 声明后,编译器会拒绝为了隐式类型转换而调用构造函数,同时显式类型转换依然合法。
总结
让编译器进行隐式类型转换所带来的弊端要大于它所带来的好处,因此除非确实需要,否则不要定义隐式类型转换函数。