引入
如果类具有带一个参数的构造函数,或是如果除了一个参数之外的所有参数都具有默认值,则参数类型可以隐式转换为类类型(这样的构造函数也叫做隐式构造函数)。 例如,如果 Box 类具有一个类似于下面这样的构造函数:
Box(int size): m_width(size), m_length(size), m_height(size){}
可以初始化 Box,如下所示:
Box b = 42;
这类转换可能在某些情况下很有用,但更常见的是,它们可能会导致代码中发生细微但严重的错误。 作为一般规则,应使用explicit 在构造函数 (和用户定义的运算符) 上使用关键字,以防止这种隐式类型转换:(也就是显式构造函数)
explicit Box(int size): m_width(size), m_length(size), m_height(size){}
构造函数是显式函数时,此行会导致编译器错误:ShippingOrder so(42, 10.8);
作用
如果构造函数中带有explicit,则它只能用于初始化和显式类型转换。比如:
class Date{
int d, m, y;
public:
explicit Date(int dd = 0, int mm = 0, int yy = 0);
};;
Date d1{15}; //正确,被看成是显式类型转换
Date d2 = Date{15}; //正确,被看成是显式类型转换
Date d3 = {15}; //error:=初始化不能进行隐式类型转换
Date d4 = 15; //error:=初始化不能进行隐式类型转换
void f(){
my_fct(15); //error:参数传递不能进行隐式类型转换
my_fct({15}); //error:参数传递不能进行隐式类型转换
my_fnt(Date{15}); // ok :显式类型转换
}
用=进行初始化可以看作拷贝初始化。一般来说,初始化器的副本会被放入待初始化的对象。但是,如果初始化器是一个右值,这种拷贝可能会被优化掉,而采用移动操作。省略=会将初始化变为显式初始化。显式初始化也叫做直接初始化*
默认情况下,应该将单参数的构造函数声明为explicit。除非你有很好的理由(比如complex复数:如果忽略虚部,就会得到实数轴上的一个复数+)。如果定义隐式构造函数,那就写下原因。
如果一个构造函数声明为explicit而且定义在类外,则在定义中不能重复explicit:
class Date{
int d, m, y;
public:
explicit Date(int d);
};
Date::Date(int d) {...} //ok
explicit Date::Date(int d) {...} //error
大多数explicit起很重要作用的构造函数都接受单一参数。但是,explicit也可以用于无参或多个参数的构造函数:
struct x{
explicit x();
explicit x(int, int);
};
x x0 = {}; //错误,隐式的
x x1 = {1, 2}; //错误,隐式的
x x3{}; //正确,显式的
x x4{1,2}; //正确,显式的
int f(x);
int i1 = f({}); //错误,隐式的
int i2 = f({1,2}); //错误,隐式的
int i3 = f(x{}); //正确,显式的
int i4 = f(x{1.2}); //正确,显式的
列表初始化也存在直接初始化和拷贝初始化的区别