转自:https://blog.csdn.net/tianmingdyx/article/details/79823470
1 莫名其妙的explicit
在很多C++代码中,定义类的构造函数的时候,往往会在前面加一个explicit关键字。先来看cppreference上面对它的解释:
The explicit specifier specifies that a constructor or conversion function (since C++11) doesn't allow implicit conversions or copy-initialization. It may only appear within the decl-specifier-seq of the declaration of such a function within its class definition.
大体意思是说,用explicit修饰构造函数的作用是禁止隐式转换或复制初始化。那什么是隐式转换和复制初始化呢?这要从类的构造函数和初始化过程说起。
2 构造函数和初始化过程
在C++中,变量的初始化有两种方式:直接初始化(用()运算符,如int a(1);)和复制初始化(用=运算符,如int a = 1;)。在编译和代码运行过程中,这两种初始化是有区别的。先来看一个例子:
// 实例2
#include<iostream>
using namespace std;
class A
{
public:
A(){ cout << "f1" << endl;} // 无参数构造函数,记为f1
A(int temp){ cout << "f2" << endl; } // 单参数构造函数,记为f2
A(const A& temp){ cout << "f3" << endl;} // 复制构造函数,记为f3
private:
int a;
};
int main()
{
A a1; // 调用f1
A a2(1); // 调用f2
A a3(a2); // 调用f3
cout << "------" << endl;
A a4 = 1; // 隐式转换
cout << "------" << endl;
A a5 = a2; // 拷贝初始化
return 0;
}
实例1代码可以编译通过并运行,运行结果如下:(gcc6.3)
f1
f2
f3
------
f2
------
f3
从运行结果可以看出,在直接初始化(即main函数的第二三句代码)过程中,会根据()中的数据的类型去调用不同构造函数,其中就包括复制构造函数。A a4 = 1和A a5 = a2这两句代码是复制初始化。但是其中的A a4 = 1这句代码令人困惑,为什么一个数字可以初始化一个类对象呢?其实这个地方发生了隐式转换。
3 隐式转换
看一下cppreference中对隐式转换的解释:
Implicit conversions are performed whenever an expression of some type T1 is used in context that does not accept that type, but accepts some other type T2; in particular:
* when the expression is used as the argument when calling a function that is declared with T2 as parameter;
* when the expression is used as an operand with an operator that expects T2;
* when initializing a new object of type T2, including return statement in a function returning T2;
* when the expression is used in a switch statement (T2 is integral type);
* when the expression is used in an if statement or a loop (T2 is bool).
其中高亮的那一条,指的就是我们在实例1中遇到的A a4 = 1情况。通过代码运行的结果我们可以看出,上面这两句实际上被编译器隐式的转换成了下面的过程。(具体的过程可能跟编译器有关,有资料说A a4 = 1会先调用f2,构造出一个临时变量temp,再调用f3将temp复制给a4,带这里实际测试结果是只调用的f2)
A a4(1);
A a5(a2);
虽然上面的代码是可以正常运行的,但是像A a4 = 1这样的代码的可读性非常的差,因此,为了避免出现这种情况,我们可以使用explicit关键字来修饰构造函数,从而禁止隐式转换和复制初始化。
4 使用explicit关键字
将实例1的代码做如下的修改:
// 实例2
#include<iostream>
using namespace std;
class A
{
public:
A(){ cout << "f1" << endl;} // 无参数构造函数,记为f1
explicit A(int temp){ cout << "f2" << endl; } // 单参数构造函数,记为f2
explicit A(const A& temp){ cout << "f3" << endl;} // 复制构造函数,记为f3
private:
int a;
};
int main()
{
A a1; // 调用f1
A a2(1); // 调用f2
A a3(a2); // 调用f3
cout << "------" << endl;
A a4 = 1; // 隐式转换
cout << "------" << endl;
A a5 = a2; // 拷贝初始化
return 0;
}
在单参数构造函数和拷贝构造函数之前加上explicit关键字,然后再编译代码。这时,编译不能通过,并会报出如下的错误:
prog.cpp: In function ‘int main()’:
prog.cpp:22:12: error: conversion from ‘int’ to non-scalar type ‘A’ requested
A a4 = 1; // 隐式转换
^
prog.cpp:24:12: error: no matching function for call to ‘A::A(A&)’
A a5 = a2; // 拷贝初始化
这就是因为explicit关键字禁止了隐式转换和复制初始化,因此编译不能通过。注释掉A a4 = 1和A a5 = a2这两句代码就可以编译通过了。 通过使用explicit关键字,可以强制性的要求类的使用者按照规范的方式编写代码,提高代码的可读性,并降低发生错误的风险。
5 注意
- 复制构造函数又被叫做拷贝构造函数(copy constructor),复制初始化也叫做拷贝初始化。
- 拷贝构造函数和拷贝初始化不要混淆。拷贝初始化过程要调用拷贝构造函数,但拷贝构造函数也可以用在直接初始化过程。
- explicit关键字只对单形参的构造函数起作用,对于多形参的构造函数,不会出现隐式转换的问题。 《C++ Primer》中有指出“可以用单个实参来调用的构造函数定义了从形参类型到该类型的一个隐式转换。”
- 隐式转换不仅会出现在初始化过程中,另一个很常见的情况是以类类型为形参的函数的参数传递过程,比如下面的代码,如果加上explicit关键字,是不能编译通过的。
// 实例3
#include<iostream>
using namespace std;
class A
{
public:
A(){ cout << "f1" << endl;} // 无参数构造函数,记为f1
A(int temp){ cout << "f2" << endl; } // 单参数构造函数,记为f2
A(const A& temp){ cout << "f3" << endl;} // 复制构造函数,记为f3
int x;
};
A fun(A a){a.x = 1; return a;}
int main()
{
A ret;
cout << "----------" << endl;
ret = fun(1);
return 0;
}
运行结果:
f1
----------
f2
f3