隐式转换有点像是“预处理”,以函数调用为例,函数调用的参数是严格的匹配的,假设函数接受一个double
参数,在调用之时若传入了一个int
,隐式转换,将一个不匹配的参数“预处理”成符合的参数,在本例,隐式转换就将int转换成double
,对于一个强语言来说,这将产生一个错误[1]。就这个层面上来讲,C++
作为一个弱语言,定义隐式转换规则有利于:
- 减少函数的书写。严格匹配,我还需要额外定义int类型支持,可能这对于该函数没有差别
- 方便调用者调用。这种模糊,帮助了程序员的同时也带来了不符合预期结果。[2]
对于基本数据类型,C++
定义了保证精度的转换规则;对于STL标准库中的容器,定义了必要的转换规则,这些规则往往就是非explicit
构造函数。
当用户在自定义一个类时,如果构造函数的参数实际作用只有一个[3],使用该类作为参数的函数,将会启用类的隐式转换规则:那么编译器将会在没有被声明为explicit关键字构造函数寻找匹配的构造函数,将不符合的参数“预处理”(构造)成一个临时对象,继续进行函数的匹配。如果我们不想要这个构造函数成为隐式转换的规则,那么将其声明为explicit
即可,看上去explicit
的声明将规则隐藏了。 特别地,拷贝构造函数也是一个函数,在使用拷贝构造时,参数也可以发生隐式转换。
class A
{
public:
A(int i){...}
A(const A&a){...}
}
就好像这样是用int
进行拷贝构造一样。
A a=1;
在这里A a=1;
拷贝构造输入的参数是一个int
,不是拷贝构造期望的类对象,编译器将会尝试构造一个临时对象以完成拷贝过程,于是编译器就拿着这个参数int
比对现有的构造函数,完成临时对象的构造,构造完成后重新传入拷贝构造函数。这样一来就完成了拷贝构造的隐式转换。这里(非拷贝、单参数)构造函数实际上就是定义了类的一个转换规则。
如果我们不想让部分构造函数成为转换规则,只需要在声明最前边加上explicit
即可。
一般而言,构造一个对象由两类方法,一是直接构造,二是拷贝构造。拷贝构造接收自身类类型,意味着可以利用直接构造来完成隐式转换,从而完成拷贝构造,看上去我们拷贝构造能完成与直接构造相同的功能。
但是,拷贝构造替代直接构造有一个缺陷,那就是效率较低。有时候使用拷贝隐式构造是没有必要的,因为你既然要初始化一个对象,为什么还要构造一个临时对象再进行拷贝构造(多了一次拷贝构造),这就是为什么编译器要将隐式拷贝构造优化成直接构造的原因[4]。通过将构造函数声明为explicit
,就是希望你通过直接构造而不是拷贝构造方式来完成构造(构造后->拷贝构造),这可能是一种性能优化的方式。注意到智能指针shared_ptr
将参数为普通指针构造函数设置为explicit
,这就意味着,如果你如果想通过指针类型来初始化一个shared_ptr只能通过直接构造的方式来完成,或许就是为了减少一次拷贝构造吧?
[1] 《什么是弱类型语言、强类型语言?》https://www.jianshu.com/p/6191e15de0bd [2] 《C语言入坑指南-整型的隐式转换与溢出》https://cloud.tencent.com/developer/article/1497319 [3] 如果构造函数只有一个参数没有默认值也算是一个实际参数 [4] 编译器可以但不是必须这么做,依赖具体实现