zl程序教程

您现在的位置是:首页 >  后端

当前栏目

explicit可以提升构造对象效率

效率对象 提升 可以 构造 explicit
2023-09-27 14:27:31 时间

一、隐式转换是什么?

隐式转换有点像是“预处理”,以函数调用为例,函数调用的参数是严格的匹配的,假设函数接受一个double参数,在调用之时若传入了一个int,隐式转换,将一个不匹配的参数“预处理”成符合的参数,在本例,隐式转换就将int转换成double,对于一个强语言来说,这将产生一个错误[1]。就这个层面上来讲,C++作为一个弱语言,定义隐式转换规则有利于:

  • 减少函数的书写。严格匹配,我还需要额外定义int类型支持,可能这对于该函数没有差别
  • 方便调用者调用。这种模糊,帮助了程序员的同时也带来了不符合预期结果。[2]

二、C、C++定义了有限的隐式转换

对于基本数据类型,C++定义了保证精度的转换规则;对于STL标准库中的容器,定义了必要的转换规则,这些规则往往就是非explicit构造函数。

三、类的隐式转换过程及explicit关键字

3.1 隐式转换过程

当用户在自定义一个类时,如果构造函数的参数实际作用只有一个[3],使用该类作为参数的函数,将会启用类的隐式转换规则:那么编译器将会在没有被声明为explicit关键字构造函数寻找匹配的构造函数,将不符合的参数“预处理”(构造)成一个临时对象,继续进行函数的匹配。如果我们不想要这个构造函数成为隐式转换的规则,那么将其声明为explicit即可,看上去explicit的声明将规则隐藏了。
特别地,拷贝构造函数也是一个函数,在使用拷贝构造时,参数也可以发生隐式转换。

class A
{
public:
    A(int i){...}
    A(const A&a){...}
}

就好像这样是用int进行拷贝构造一样。

A a=1;

在这里A a=1;拷贝构造输入的参数是一个int,不是拷贝构造期望的类对象,编译器将会尝试构造一个临时对象以完成拷贝过程,于是编译器就拿着这个参数int比对现有的构造函数,完成临时对象的构造,构造完成后重新传入拷贝构造函数。这样一来就完成了拷贝构造的隐式转换。这里(非拷贝、单参数)构造函数实际上就是定义了类的一个转换规则。

3.2 explicit声明构造函数提高效率

如果我们不想让部分构造函数成为转换规则,只需要在声明最前边加上explicit即可。

一般而言,构造一个对象由两类方法,一是直接构造,二是拷贝构造。拷贝构造接收自身类类型,意味着可以利用直接构造来完成隐式转换,从而完成拷贝构造,看上去我们拷贝构造能完成与直接构造相同的功能。

但是,拷贝构造替代直接构造有一个缺陷,那就是效率较低。有时候使用拷贝隐式构造是没有必要的,因为你既然要初始化一个对象,为什么还要构造一个临时对象再进行拷贝构造(多了一次拷贝构造),这就是为什么编译器要将隐式拷贝构造优化成直接构造的原因[4]。通过将构造函数声明为explicit,就是希望你通过直接构造而不是拷贝构造方式来完成构造(构造后->拷贝构造),这可能是一种性能优化的方式。注意到智能指针shared_ptr<T>将参数为普通指针构造函数设置为explicit,这就意味着,如果你如果想通过指针类型来初始化一个shared_ptr只能通过直接构造的方式来完成,或许就是为了减少一次拷贝构造吧?

[1] 《什么是弱类型语言、强类型语言?》https://www.jianshu.com/p/6191e15de0bd
[2] 《C语言入坑指南-整型的隐式转换与溢出》https://cloud.tencent.com/developer/article/1497319
[3] 如果构造函数只有一个参数没有默认值也算是一个实际参数
[4] 编译器可以但不是必须这么做,依赖具体实现