C++类的隐式类型转换和explicit关键字
C++类的隐式类型转换
当我们为一个C++ class定义单参数的构造函数时,该构造函数实际上也同时定义了隐式的类型转换,即将参数类型转换为所定义的class类型,这个隐式的转换函数称为转换构造函数。如下,定义一个Person.h中定义一个Person类
#ifndef PERSON_H
#define PERSON_H
#include <string>
class Person
{
public:
Person(std::string name) : name_(name) {}
std::string getName()
{
return name_;
}
private:
std::string name_;
};
#endif
在main.cpp中定义这个类的对象,并且打印对象的name_。
#include <iostream>
#include "Person.h"
using namespace std;
int main()
{
string nm = "Jack";
Person Jack(nm);//显式由string对象初始化Person对象
std::cout<<Jack.getName()<<std::endl;
string rs = "Rose";
Person B = rs;//隐式类型转换,将string对象转换为一个Person临时对象,并拷贝构造给对象B
std::cout<<B.getName()<<std::endl;
return 0;
}
编译运行都没有问题
编译结果:
ubuntu:~/exception_test/build$ make -j10
Scanning dependencies of target ex_test
[ 50%] Building CXX object CMakeFiles/ex_test.dir/main.cpp.o
[100%] Linking CXX executable ex_test
[100%] Built target ex_test
运行结果:
ubuntu:~/exception_test/build$ ./ex_test
Jack
Rose
添加一个Person C,编译运行都OK。
#include <iostream>
#include "Person.h"
using namespace std;
int main()
{
string nm = "Jack";
Person Jack(nm);
std::cout<<Jack.getName()<<std::endl;
string rs = "Rose";
Person B = rs;
std::cout<<B.getName()<<std::endl;
Person C("LiLei");//LiLei是char型字符串,隐式的转换为string类型,string类型又显式的初始化 //了C
return 0;
}
再添加一个Person D
#include <iostream>
#include "Person.h"
using namespace std;
int main()
{
string nm = "Jack";
Person Jack(nm);
std::cout<<Jack.getName()<<std::endl;
string rs = "Rose";
Person B = rs;
std::cout<<B.getName()<<std::endl;
Person C("LiLei");
Person D = "Trump";
return 0;
}
此时编译编译就出问题。看来这个Trump是来捣乱的啊!Why? 编译log如下:log的5~7行确实显示Trump的问题。
ubuntu:~/exception_test/build$ make -j10
Scanning dependencies of target ex_test
[ 50%] Building CXX object CMakeFiles/ex_test.dir/main.cpp.o
~/exception_test/main.cpp: In function ‘int main()’:
~/exception_test/main.cpp:16:16: error: conversion from ‘const char [6]’ to non-scalar type ‘Person’ requested
Person D = "Trump";
^
CMakeFiles/ex_test.dir/build.make:62: recipe for target 'CMakeFiles/ex_test.dir/main.cpp.o' failed
make[2]: *** [CMakeFiles/ex_test.dir/main.cpp.o] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/ex_test.dir/all' failed
make[1]: *** [CMakeFiles/ex_test.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
这是因为C++只能进行一次隐式类型转换。string 类型有一个接收一个const char *型的参数构造函数,该函数不是explicit的。因此有“LiLei”隐式的转换为string对象,再由string对象初始化了Person C对象是没有问题的。但是Person D需要执行两次隐式类型转换,所以报错了。
explicit关键字
explicit关键字可以避免编译器为我们定义隐式类型转换函数。如下在构造函数前添加了explicit关键字。
#ifndef PERSON_H
#define PERSON_H
#include <string>
class Person
{
public:
//添加了explicit关键字
explicit Person(std::string name) : name_(name) {}
std::string getName()
{
return name_;
}
private:
std::string name_;
};
#endif
编译如下的代码就会报错了
#include <iostream>
#include "Person.h"
using namespace std;
int main()
{
string nm = "Jack";
Person Jack(nm);
std::cout<<Jack.getName()<<std::endl;
string rs = "Rose";
Person B = rs;
std::cout<<B.getName()<<std::endl;
return 0;
}
编译log如下:显示Person B出错。添加了explicit关键字后类的初始化就只能显式的初始化了。
ubuntu:~/exception_test/build$ make -j10
Scanning dependencies of target ex_test
[ 50%] Building CXX object CMakeFiles/ex_test.dir/main.cpp.o
~/exception_test/main.cpp: In function ‘int main()’:
~/exception_test/main.cpp:12:16: error: conversion from ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’ to non-scalar type ‘Person’ requested
Person B = rs;
^
CMakeFiles/ex_test.dir/build.make:62: recipe for target 'CMakeFiles/ex_test.dir/main.cpp.o' failed
make[2]: *** [CMakeFiles/ex_test.dir/main.cpp.o] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/ex_test.dir/all' failed
make[1]: *** [CMakeFiles/ex_test.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
explicit关键字只需要在函数声明的地方添加上就可以了,不要在函数定义的地方再次添加
google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换。所以google约定所有单参数的构造函数都必须是显示的,只有极少数情况下拷贝构造函数可以不声明称explicit。例如作为其他类的透明包装器的类。