技术: C++11 explicit注意事项

避免根据参数隐式转换, 隐式类型提升, 调用(拷贝)构造器; 简而言之, 禁止编译器自动调用相关的构造器.

下面分单参数多参数的情况, 分别探讨一下.

单参数

单个参数的情况, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo  
{
public:
explicit Foo(int a) {}
private:
Foo(const Foo &);
};

int main(void)
{
Foo a1(123); //调用Foo(int)构造函数初始化
Foo a2 = 123; //error
}

上面 error 的原因:
Foo的拷贝构造函数声明为私有的,该处的初始化方式是隐式调用Foo(int)构造函数生成一个临时的匿名对象,再调用拷贝构造函数完成初始化; (有的编译器会进行优化, 不生成临时对象, 直接生成最终对象而不调用拷贝构造器)

如果explicit Foo(int a) {} 没有explicit修饰, 即Foo(int a) {}或者Foo(int a=0) {}, 那么编译器见到Foo a2 = 123;, 会想办法进行类型提升&隐式转换.

多参数

多个参数的情况:
其实默认的情况, 只有单个实参(non-explicit, one argument)才会做自动转换, 需要加上explicit关键字强调一下, 禁止自动调用.

但是, 你不能调用, 不代表编译器不会调用

什么情况? 比如说, 有initializer-list的情况, 下面举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <initializer_list>

class T
{
public:
T(int a, int b) {std::cout << "a, b" << std::endl;}
T(std::initializer_list<int> list) {std::cout << "initializer-list" << std::endl;}

explicit T(int a, int b, int c) {std::cout << "explicit a, b, c" << std::endl;}
};

void fun(const T& t){}

int main()
{
T t{1,2,3}; //调用T(std::initializer_list<int> list)
T t1 = {1,2,3};
fun({1,2,3});
return 0;
}

运行结果:

1
2
3
4
5
6
7
main.cpp: In function 'int main()':
main.cpp:19:4: warning: variable 't1' set but not used [-Wunused-but-set-variable]
T t1 = {1,2,3};
^~
initializer-list
initializer-list
initializer-list

和预期一样, 只要存在接收initializer-list为参数的构造器, 就直接使用它, 所以不会去调用explicit T(int a, int b, int c).

但是一旦当你取消了T(std::initializer_list<int> list)这个构造器, 那么编译器会自动拆分大括号的参数, 去调用explicit T(int a, int b, int c),
结果就报错了:

1
2
3
4
5
6
7
8
main.cpp: In function 'int main()':
main.cpp:19:15: error: converting to 'T' from initializer list would use explicit constructor
'T::T(int, int, int)'
T t1 = {1,2,3};
^
main.cpp:20:13: error: converting to 'const T' from initializer list would use explicit
constructor 'T::T(int, int, int)'
fun({1,2,3});

也就是说, 编译器去隐式调用了, 也就是说:

一旦指定了 explicit 的构造器, 即便连编译器也不允许隐式调用; 统统要显示的调用.

文章目录
  1. 1. 单参数
  2. 2. 多参数
|