1. 隐式类型转换
C++编译器能在两种数据类型之间进行隐式转换(implicit conversion),它从C语言继承过来的转换方法,例如char隐式转换为int和short转换为double之类的,对于用户自定义类型,则可以自己提供函数让编译器进行隐式类型转换。
通过单参数构造函数(single-argument constructors)和隐式类型转换运算符两种方式,我们可以允许编译器进行这些隐式转换。
隐式类型转换运算符就是一个”operator 类型符号“格式的成员函数,不需要定义函数的返回类型,因为返回类型就是这个函数的名字,例如当我们定义了成员函数“operator double() const”,则该函数就是用于将对象隐式转换为double类型。
class Test() {
public:
...
operator double() const;
};
Test a;
double d = a * 2;
上面代码“double d = a * 2;”中,就会通过double()函数成员函数将对象a隐式转换为一个double变量,然后用其值计算出最后的d值。
单参数构造函数是指只用一个参数即可调用的构造函数,该函数可以是只定义了一个参数也可以是定义了多个参数但第一个参数以后的所有参数都有缺省值。隐式类型转换就发生在我们把该构造函数参数类型的值赋给该类型对象的时候,编译器会自动帮我们调用该单参数构造函数来构造对象。
#include <iostream>
#include <vector>
class Test {
public:
Test() {
_mem = 10;
}
Test(int mem) {
_mem = mem;
}
int getMem() {
return _mem;
}
private:
int _mem;
};
int main()
{
Test b;
b = 11;
std::cout << b.getMem() << std::endl;
return 0;
}
在上面的代码中,虽然b变量的类型是Test,但是由于其单参数构造函数Test(int mem)的存在,当执行到"b = 11"的时候,编译器会认为可以从int类型隐式转换到Test类型。最终调用了Test(int mem)构造函数,使_mem的值变成了11,所以我们可以得到b.getMem()的输出为11。
在隐式转换中,容易带来的问题的是单参数构造函数,因为隐式类型转换运算符除非显示定义它,不然是不会起作用的。而对于单参数构造函数,在我们自定义类型中,很容易出现这种构造函数。
template <class T>
class Array {
public:
Array(int size);
T& operator[](int index);
...
};
int main()
{
Array<int> a(10);
Array<int> b(10);
for (int i = 0; i < 10; ++i) {
if (a == b[i]) // 本来是a[i] == b[i]
...
else
...
}
return 0;
}
在上面的代码中,我们本来是想比较“a[i] == b[i]”,但是写错成了“a == b[i]”,由于单参数构造函数“Array(int size)”的存在,编译器并不会有任何报错,相反的会用单参数构造函数将b[i]转换为一个Array<int>的临时对象,然后再将其与a对比。
所以在上面的代码中,每次对比都会导致一个Array<int>变量的构造和销毁,导致了很多不必要的开销,显然这是我们不想看到的。
2. explicit关键字
既然单参数构造函数引发的隐式类型转换可能会导致错误,那怎么避免呢?就需要用到explicit关键字了,将单参数构造函数显示声明为explicit的,则编译器就会拒绝为了隐式类型转换而调用构造函数,但是显示类型转换则仍然是合法的。
template <class T>
class Array {
public:
explicit Array(int size);
T& operator[](int index);
...
};
int main()
{
Array<int> a(10);
Array<int> b(10);
for (int i = 0; i < 10; ++i) {
if (a == b[i]) // ins 1
...
else
...
}
if (a == Array<int>(b[1])) { // ins 2
...
}
if (a == static_cast<Array<int> >(b[i])) { // ins 3
...
}
return 0;
}
上面的代码中,显示类型转换ins 2和ins 3能够正常编译,而ins 1则会隐式转换失败(编译的时候会报错)。
如果编译器不支持explicit关键字,像《More Effective C++》所说的就是定义一个新的类型来作为单参数构造函数的参数类型,就可以避免编译器自动完成的隐式类型转换了。