C++此前编译器默认的隐式类型转换机制,在解决程序的泛型应用上起到了不小的帮助,但是这种隐式的类型转换有可能会导致潜在的问题。举代码如下
#include <iostream>
using namespace std;
struct Rational_1 {
Rational_1(int n=0, int d=1) : num(n), den(d) {
cout << __func__ << "(" << num << "/" << den << ")" << endl;
}
int num; //Numerator被除数
int den; //Denominator除数
};
struct Rational_2 {
explicit Rational_2(int n=0, int d=1) : num(n), den(d) {
//加了explicit关键字,代表该构造函数不能被隐式调用,必须显式调用才能运行
cout << __func__ << "(" << num << "/" << den << ")" << endl;
}
int num; //Numerator被除数
int den; //Denominator除数
};
void Display1(Rational_1 ra){
cout << "Numerator: " << ra.num << " Denominator: " << ra.den << endl;
}
void Display2(Rational_2 ra){
cout << "Numerator: " << ra.num << " Denominator: " << ra.den << endl;
}
template <typename T>
class Ptr {
public:
Ptr(T* p) : _p(p) {}
//定义一个从Ptr类型转换成bool类型的函数
explicit operator bool() const {
if (_p != 0)
return true;
else
return false;
}
private:
T* _p;
};
/*************************************************************************
**1. explicit 关键字修饰函数,将会禁止函数被隐式调用
** 有些情况下,函数的隐式调用会导致意想不到的问题出现,
** 经常出现问题的场景有:拷贝构造函数的隐式传递调用 和 非显式的类型转换
*************************************************************************/
int main()
{
Rational_1 r1_1 = 11; //该表达式理应是调用拷贝赋值函数,但是因为r1_1并无初始化,所以将隐式调用带参构造函数
Rational_1 r1_2(12); //显式调用带参构造函数
//Rational_2 r2_1 = 21; //error: conversion from 'int' to non-scalar type 'Rational_2' requested
Rational_2 r2_2(22); //显式调用带参构造函数,可以编译通过
Display1(1); //参数传递,将隐式调用构造函数
//Display2(2); //无法通过编译,could not convert '2' from 'int' to 'Rational_2'
Display2(Rational_2(2)); //显式调用构造函数传递临时对象
int a;
Ptr<int> p(&a);
if (p)
cout << "valid pointer." <<endl; //valid pointer
else
cout << "invalid pointer." << endl;
Ptr<double> pd(0);
cout << p+pd << endl; //如果不加explicit修饰Ptr类中的转换函数,则该表达式将会隐式调用
//该转换函数,但是指针量之间的相加并无意义,但是因为转换函数的定义,该表达式将被编译通过
//所以应该给转换函数加上explicit,以避免可能存在不当使用并且难以察觉
//加上explicit修饰转换函数后,则除了在bool量应该出现的位置,是不会调用该转换函数的
//所以编译器将报错:error: no match for 'operator+' (operand types are 'Ptr<int>' and 'Ptr<double>'
return 0;
}
此外还需要说明的就是关于操作符operator可以自由定义转换类型的使用情况(operator可以定义不仅仅是+/-/*/bool,还可以是任何其他的类型符合),结合上面的explicit可以给出如下的代码
#include <iostream>
using namespace std;
class ConvertTo {}; //虽然看起来ConvertTo类是空的,但是编译器会为其默认构建一系列的默认构造函数,包括拷贝构造、移动赋值、拷贝赋值等
class Convertable {
public:
explicit operator ConvertTo () const { return ConvertTo(); }
//定义Convertable类型向ConvertTo类型的转换函数
};
void Func(ConvertTo ct) {}
int main()
{
Convertable c;
ConvertTo ct(c);
//直接初始化,直接调用ConvertTo的拷贝构造函数,因为类Convertable存在显式定义的转换函数,故而
//将会触发调用ConvertTo::ConvertTo(ConvertTo&)拷贝构造函数,pass
ConvertTo ct2 = c;
//按照格式来看,该表达式应该是触发调用ConvertTo::operator=(const ConvertTo&)拷贝赋值
//但是因为ct2并无初始化,所以将会隐式触发调用ConvertTo::ConvertTo(ConvertTo&)拷贝构造函数
//但是此时因为不是显式调用,故而无法显式地调用Convertable的转换函数,从而将会出现
//error: conversion from 'Convertable' to non-scalar type 'ConvertTo' requested
ConvertTo ct3 = static_cast<ConvertTo>(c); //强制类型转化,通过
Func(c); //could not convert 'c' from 'Convertable' to 'ConvertTo'
//同样的道理,形参传递,类型不符合理应是调用拷贝构造函数,但是因为并非显式转换,coder没有表达出
//显式调用转换函数的语义,故而这里将不会触发Convertable类的转换函数
Func(static_cast<ConvertTo>(c)); //这样子的写法,将‘显式调用类型转换函数’的语义表达清楚了
//从而可以编译通过
return 0;
}