参考
C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用。
C++中,只有一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。
- 一是构造
- 二是默认且隐含的类型转换操作符
所以,在我们写下如 a = b,这样的代码,且恰好b的类型正好是a单参数构造器的参数类型,这时候编译器就自动调用这个构造器,创建一个a的对象。
C++中的explicit关键字只能用于修饰只有一个参数的类构造函数,作用是表明该构造函数是显式的,而非隐式的。
与explicit对应的关键字是implicit,意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式的)
只能在类内声明构造函数时使用explicit关键字
显式构造函数和隐式构造函数的区别
// 没有使用explicit关键字的类声明, 即默认为隐式声明
class CxString {
public:
CxString(int size) {
_size = size;//string的预设大小
_pstr = malloc(size + 1);//分配string的内存
memset(_pstr, 0, size + 1);//初始化函数,在一段内存块中填充某个给定的值
}
CxString(const char *p) {
int size = strlen(p);
_pstr = malloc(size + 1);//分配string的内存
strcpy(_pstr, p);//复制字符串
_size = strlen(_pstr);
}
// 析构函数这里不讨论, 省略...
private:
char *_pstr;
int _size;
};
// 下面是调用:
CxString string1(24);//这样是OK的, 为CxString预分配24字节的大小的内存
CxString string2 = 10;//这样是OK的, 为CxString预分配10字节的大小的内存
CxString string3;//这样是不行的, 因为没有默认构造函数, 错误为: “CxString”: 没有合适的默认构造函数可用
CxString string4("aaaa");//这样是OK的
CxString string5 = "bbb";//这样也是OK的, 调用的是CxString(const char *p)
CxString string6 = 'c';//这样也是OK的, 其实调用的是CxString(int size), 且size等于'c'的ascii码
string1 = 2;//这样也是OK的, 为CxString预分配2字节的大小的内存
string2 = 3;//这样也是OK的, 为CxString预分配3字节的大小的内存
string3 = string1;//这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用free释放_pstr内存指针的时候可能会报错, 完整的代码必须重载运算符"=", 并在其中处理内存释放
-
注意CxString string2 = 10这句
发生隐式转换的一种情况是执行拷贝形式的初始化时(使用=)
在C++中,如果构造函数只有一个参数时,那么编译的时候会有一个缺省的转换操作,将该构造函数对应数据类型的数据转换为该类对象。
也就是说CxString string2 = 10;这段代码,编译器自动将整型转换为CxString类对象,实际上等同于下面的操作:
CxString string2(10); 或 CxString temp(10); CxString string2 = temp;
但是上面代码中的_size代表的是字符串内存分配的大小,那么调用CxString string2 = 10;和CxString string6 = ‘c’;就显得不伦不类了,而且容易让人疑惑。
使用explicit关键字,阻止这种方法。
当我们使用explicit关键字声明构造函数时,只能以直接初始化的形式显式的使用
对上述代码修改:
// 使用关键字explicit的类声明, 显示转换
class CxString {
public:
explicit CxString(int size)
{
_size = size;//string的预设大小
_pstr = malloc(size + 1);//分配string的内存
memset(_pstr, 0, size + 1);//初始化函数,在一段内存块中填充某个给定的值
}
CxString(const char *p)
{
int size = strlen(p);
_pstr = malloc(size + 1);//分配string的内存
strcpy(_pstr, p);//复制字符串
_size = strlen(_pstr);
}
private:
char *_pstr;
int _size;
};
// 下面是调用:
CxString string1(24);//这样是OK的
CxString string2 = 10;//这样是不行的, 因为explicit关键字取消了隐式转换
CxString string3;//这样是不行的, 因为没有默认构造函数
CxString string4("aaaa");//这样是OK的
CxString string5 = "bbb";//这样也是OK的, 调用的是CxString(const char *p)
CxString string6 = 'c';//这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换
string1 = 2;//这样也是不行的, 因为取消了隐式转换
string2 = 3;//这样也是不行的, 因为取消了隐式转换
string3 = string1;//这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载
- explicit关键字的作用就是防止类构造函数的隐式自动转换
- explicit关键字只对有一个参数的类构造函数有效,如果类构造函数参数大于或等于两个,是不会产生隐式转换的,所以explicit关键字也就无效了,有一种特殊情况,当除了第一个参数以外的其他参数都是默认值的时候,explicit关键字依然有效
例外情况:
当除了第一个参数以外的其他参数都是默认值的时候,explicit关键字依然有效,此时当调用构造函数时只传入一个参数,等效于只有一个参数的类构造函数
//使用关键字explicit声明
class CxString {
public:
explicit CxString(int size, int flag = 0) {
_flag = flag;
_size = size;//string的预设大小
_pstr = malloc(size + 1);//分配string的内存
memset(_pstr, 0, size + 1);//初始化函数,在一段内存块中填充某个给定的值
}
CxString(const char *p) {
int size = strlen(p);
_pstr = malloc(size + 1);//分配string的内存
strcpy(_pstr, p);//复制字符串
_size = strlen(_pstr);
}
private:
char *_pstr;
int _size;
int _flag;
};
// 下面是调用:
CxString string1(24);//这样是OK的
CxString string2 = 10;//这样是不行的, 因为explicit关键字取消了隐式转换
CxString string3;//这样是不行的, 因为没有默认构造函数
string1 = 2;//这样也是不行的, 因为取消了隐式转换
string2 = 3;//这样也是不行的, 因为取消了隐式转换
string3 = string1;//这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载