我们在阅读C++源码时有时会看到类的构造函数前面会有explicit关键字,这个单词字面意思是显式的(与之相反的单词是implicit,隐式的)。下面就来分析下这个关键字的作用。
一 例子
首先来让我们看一段简单的C++代码,
#include <iostream>
#include <map>
using namespace std;
class Person
{
public:
Person(const string& name) : s_mName(name)
{}
string getName()
{
return s_mName;
}
private:
string s_mName;
};
class Family
{
public:
Family() {}
// 添加家庭成员
void addPerson(const string& memberName, const Person& onePerson)
{
m_mMember.insert({memberName, onePerson});
}
// 打印所有家庭成员
void printAllFamilyMember()
{
map<string, Person>::iterator iter = m_mMember.begin();
for (; iter != m_mMember.end(); ++iter)
{
cout << iter->first <<" : " << (iter->second).getName() << "\n";
}
}
private:
map<string, Person> m_mMember;
};
int main()
{
Family oneFamily;
Person father("Li Lei");
Person mother("Han Meimei");
oneFamily.addPerson("Father", father);
oneFamily.addPerson("Mother", mother);
oneFamily.addPerson("Son", string("Li Si")); // 添加儿子
oneFamily.printAllFamilyMember();
return 0;
}
这里我们定义了2个类,Person类和Family类,其中Person类用来定义一个人,Family类用来定义一个家庭,Family类可以添加家庭成员,并且可以打印家庭成员。
上面代码main函数添加了三个家庭成员,爸爸,妈妈和儿子,最终输出如下,
使用addPerson()添加儿子时,第二个实参本应该是一个Person类的类对象,但是我们传递的是一个string,其实际发生的参数传递等价于以下语句,
const Person& onePerson = string("Li Si");
这样也可以正常运行,这是为什么呢?
二 分析
这里引用一下《C++ primer 5th》里7.5.4节的一句话,
如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,有时我们把这种构造函数称作转换构造函数(converting constructor)。
还有这句,
能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。
也就是说如果一个类的构造函数只有一个参数,那么就可以从这个参数的类型隐式的转换为类类型。对上面的例子来说,Person类的构造函数参数只有一个,是string类型,当给addPerson()的第二个参数传递实参时,我们传递一个string类型的参数,那么这个string类就会隐式转换为Person类,编译器会用给定的string创建一个Person类对象,新生成的这个Person类对象会传给addPerson()。
这种隐式转换有一定的限制,就是只能转换一次。如果我们给addPerson()传递一个字符串,那么就不行了,如下,
oneFamily.addPerson("Son", "Li Si"); // 添加儿子
因为这需要字符串转为string,string再转为Person,隐式转换发生了2次,所以编译不通过。
三 explicit的作用
有了上面2节的分析,那么现在我们就可以知道explicit的作用了,就是不允许隐式转换,用户使用时必须传递原本的类类型生成的对象。我们在Person类的构造函数前加上explicit关键字,
class Person
{
public:
explicit Person(const string& name) : s_mName(name)
{}
string getName()
{
return s_mName;
}
private:
string s_mName;
};
此时再编译,添加儿子的这句代码就出错了。因为这个构造函数不允许隐式的创建类对象,必须显示的创建类对象。
那么添加儿子的写法就可以改为以下2种,
oneFamily.addPerson("Son", Person("Li Si")); // 实参是一个显示构造的Person对象
或使用static_cast
oneFamily.addPerson("Son", static_cast<Person>(string("Li Si"))); // static_cast可以使用explicit的构造函数
另外要注意的是,explicit只对一个参数的构造函数有效。需要多个参数的构造函数不允许发生隐式转换(用哪个参数去转换呢?),所以就不需要把这种构造函数指定为explicit的。
最后再引用《C++ primer 5th》里的一句话,
当我们用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用。而且编译器将不会在自动转换过程中使用该构造函数。
四 总结
隐式转换其实我们平时用的比较多,当一个函数的参数是string类型,我们一般图方便都是直接传递字符串,这就发生了隐式转换。本文使用一个例子简单分析了explicit关键字的作用,总结下就是不允许发生从实参类型到类类型的隐式转换。
如果有写的不对的地方,希望能留言指正,谢谢阅读。