explicit研究
explicit是C++中的关键字,不是C语言中的。英文直译是“明确的”、“显式的”意思。出现这个关键字的原因,是在C++中有这样规定的基础上:当定义了只有一个参数的构造函数时,同时也定义了一种隐式的类型转换。先看类型转换。
隐式转换的场景
等于号与构造函数
比如你有一个类的对象A:
class A
{
public:
A(int i)
{
a = i;
}
int getValue()
{
return a;
};
private:
int a;
};
你会发现,你在main函数中,使用下面的语句时是合法的:
A a;
a = 10;
之所以类A的对象可以直接使用整型通过等于号来初始化,是因为这一语句调用了默认的单参数构造函数,这种构造函数又称 类型转换构造函数。其效果等价于A temp(10); a(temp);
首先编译器执行A temp(10);在栈中创建了一个临时对象(假设叫做temp)。然后再调用对象a的拷贝初始化构造函数 a(temp) 给a初始化。然后临时对象temp销毁。这就是编译器做的隐式转换工作。你可以想到这样的隐式操作的结果和直接显示调用A a(10);的结果是一样的,但是隐式转换因为使用了拷贝构造函数所以在开销上会更高一些。当然这基本数据类型,或许不明显。如果一个复杂的对象,比如Qt的窗口。那么开销可想而知。
注意当你使用A a = 10;时并不会产生中间的临时对象。而是直接把10作为参数传递给类型转换构造函数。
又如当你使用如下语句会不通过:
A a = "123";
因为没有参数为字符串的单参数构造函数。知道了这个,你修改一下就能通过了。
class A
{
public:
A(int i)
{
a = i;
}
A(char * c)
{
a=c[0];
}
int getValue()
{
return a;
};
private:
int a;
};
函数调用
我们再定义一个函数print 用来打印A对象的值。
void print(A a)
{
cout<<a.getValue();
};
在main函数中:
void main()
{
print(10);
}
这样是可以编译运行的。虽然我们并没有创建一个类A的对象来传给print 函数。但是编译器默认会调用类A的单参数构造函数,创建出一个类A的对象出来。
加上explicit
上面可以看出编译器会为你做一些,隐式的类型转换工作。这或许会让你感到方便,但是有时候却会带来麻烦。我们来做一个假设:
上面这个类A,只接受整型和字符串型。所以你想传递一个字符串 “2” 作为参数来构造一个新的对象。比如 A b = “2”; 然而,你却写错了,双引号写成了单引号变成了 A b = ‘2’; 然而这样并不会报错。编译器 会把 字符 ‘2’ 转型成 整型 也就是它的ascll码—— 50。为了避免这样我们不希望的隐式转换,我们可以加上explicit 关键字。
public:
explicit A(int i)
{
a=i;
}
这样就能避免隐式的类型转换了,当你误写成单引号的时候,就会报错。这样就只允许显示的调用单参数构造函数了。如 A a(10); A b("123");
不仅如此,在加上explicit之后,print函数也会报错了。因为编译器不会主动调用explicit标识的构造器。这就需要你自己显示的来调用了:
print(A(10));
当然了,是否应该禁止隐式转换是没有定论的,没有一种放之四海皆准的标准,具体看你的情景需要了。
一般而言,显示调用构造器,能避免一些麻烦,让程序员手动来管理。很多人说C++难,因为很多东西对于程序员来说不是透明的,比如内存释放什么的,这个显式调用也是需要程序员自己动手的。然而我感觉这正是C++的魅力所在,C++给了程序员几乎等同于上帝的权力,所有一切都能自己掌控,还比如运算符重载的权力。
————————————————
C语言有简单粗暴的强制类型转换,当前C++也有,问,可以将一个数字强制转换为一个类吗?
class TestCls {
public:
int a;
};
int main(void) {
TestCls t = 10;
return 0;
}
这样编译肯定有编程报错:
加上强制类型转换?
TestCls t=(TestCls)10;
编译:
提示说找不到TestCls::TestCls(int),这显然是个构造函数。定义这么一个构造函数后:
class TestCls
{
public:
int a;
TestCls(int a) {}
};
编译就通过了。
TestCla(int a)函数在这里是一个转换构造函数,转换构造函数的作用就是把传入的其他类型参数转换成类的对象。转换构造函数说白了还是一个构造函数,只不过该构造函数:
(1)有且只有一个参数
(2)参数是基本类型或者是其他类类型,也就是说不可以是本身的类型
在有转换构造的类TestCls中,我们可以不用加上强制类型转换,同样能通过编译,即:
TestCls t = 10;
那么执行这句代码时,编译器做了什么?
10这个立即数默认int类型,它被用来初始化一个类对象,显然不符合逻辑,那么编译器会先查找class中是否有int类型参数的转换构造函数,即找到TestCls(int a), 那么该行代码将会变成:
TestCls t=TestCls(10);
即编译器会调用这个转换构造函数,生成一个临时对象进而初始化对象t,这是由于编译器具有隐式类型转换功能,它会尽力尝试让程序编译通过。
但是在实际项目工程中,可能我们对于 TestCls t =10; 这样把述职直接初始化对象只是一个误操作,将10初始化对象t并非程序员本意,所以我把要去除这个编译器隐式转换的功能,这就需要explicit 关键字修饰转换构造函数。
class TestCls
{
public:
int a;
explicit TestCls(int a) {}
};
修改后再次编译:
编译器不会进行隐式转换,倘若程序员还是想要实现将10初始化对象t,那就可以使用强制类型转换:
TestCls t = (TestCls)10;
或者
TestCls t = static_cast<TestCls>(10)
参考:
https://blog.csdn.net/guodongxiaren/article/details/24455653