摘要
C++提供了关键字 explicit
,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为 explicit
的构造函数不能在隐式转换中使用
跟它相对应的另一个关键字是 implicit
,意思是隐藏的,类构造函数默认情况下即声明为 implicit
(隐式)
浅显的理解
C++中,一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数),承担了两个角色:
- 构造器
- 默认且隐含的类型转换操作符
所以,有时候在我们写下如 AAA = XXX,这样的代码,且恰好XXX的类型正好是AAA单参数构造器的参数类型,这时候编译器就自动调用这个构造器,创建一个AAA的对象
这样看起来好象很酷,很方便。但在某些情况下(见下面权威的例子),却违背了我们(程序员)的本意。这时候就要在这个构造器前面加上
explicit
修饰,指定这个构造器只能被明确的调用/使用,不能作为类型转换操作符被隐含的使用
class A {
private:
int a;
public:
A(int x = 0){ //普通构造函数
a = x;
}
};
class B {
private:
int a;
public:
explicit B(int x = 0){ //explicit(显式)构造函数
a = x;
}
};
int main() {
A a1 = 10; //正确,隐式调用构造函数
B b1 = 10; //错误,explicit修饰,不能隐式调用
//编译错误: error: conversion from 'int' to non-scalar type 'B' requested
A a2(10); //正确,显式调用构造函数
B b2(10); //正确,显式调用构造函数
}
explicit 补充
在 C++11 新标准中引入了显式的类型转换运算符(explicit conversion operator)
explicit
的功能在这里和显式的构造函数一样,编译器(通常)也不会将一个显式的类型转换运算符用于隐式类型转换
class SmallInt {
private:
std::size_t val;
public:
SmallInt(int i = 0): val(i) {...}; // 构造函数
//...其他成员的定义
//编译器不会自动执行这一类型转换
explicit operator int() const { return val; } //显式的类型转换函数
};
SmallInt si = 3; //正确:SmallInt的构造函数不是显式的
si = 4; //正确:首先将4隐式地转换成SmallInt,然后调用SmallInt::operator=
si + 3; //错误:此处需要隐式的类型转换,但类的运算符是显式的
//如果前面没有explicit,那么这里首先将si隐式转换地转换成int,然后执行整数的加法
static_cast<int>(si) + 3; //正确:显式地请求类型转换
当类型转换运算符是显式的时,我们也能执行类型转换,不过必须通过显式的强制类型转换才可以。
注意:该规定存在一个例外,即如果表达式被用作条件,则编译器会将显式的类型转换自动应用于它。换句话说,当表达式出现在下列位置时,显式的类型转换将被隐式地执行:
- if、while 及 do 语句地条件部分
- for 语句头地条件表达式
- 逻辑非运算符(!)、逻辑或运算符(||)、逻辑与运算符(&&)的运算对象
- 条件表达式(? :)的条件表达式
参考资料
[1] C++ Primer(第5版)中文版 p514 类型转换运算符