Den*_*ose 36 c++ explicit explicit-constructor default-constructor
我最近注意到C++ 0x中的一个类需要一个显式的默认构造函数.但是,我没有想出一个可以隐式调用默认构造函数的场景.这似乎是一个相当无意义的说明者.我想也许它会Class c;不赞成,Class c = Class();但似乎并非如此.
来自C++ 0x FCD的一些相关引用,因为我更容易导航[类似文本存在于C++ 03中,如果不在同一个地方]
12.3.1.3 [class.conv.ctor]
默认构造函数可以是显式构造函数; 这样的构造函数将用于执行默认初始化或值初始化(8.5).
它继续提供显式默认构造函数的示例,但它只是模仿我上面提供的示例.
8.5.6 [decl.init]
默认初始化T类型的对象意味着:
- 如果T是一个(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);
8.5.7 [decl.init]
对值类型T的对象进行值初始化意味着:
- 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);
在这两种情况下,标准都要求调用默认构造函数.但是如果默认构造函数是非显式的,那就会发生这种情况.为了完整起见:
8.5.11 [decl.init]
如果没有为对象指定初始化程序,则默认初始化该对象;
据我所知,这只是从没有数据的转换.这没有意义.我能想到的最好的是以下内容:
void function(Class c);
int main() {
function(); //implicitly convert from no parameter to a single parameter
}
Run Code Online (Sandbox Code Playgroud)
但显然这不是C++处理默认参数的方式.还有什么会使explicit Class();行为与众不同Class();?
生成此问题的具体示例是std::function[20.8.14.2 func.wrap.func].它需要几个转换构造函数,其中没有一个被标记为显式,但默认构造函数是.
Joh*_*itb 28
这声明了一个显式的默认构造函数:
struct A {
explicit A(int a1 = 0);
};
A a = 0; /* not allowed */
A b; /* allowed */
A c(0); /* allowed */
Run Code Online (Sandbox Code Playgroud)
如果没有参数,如下例所示,则explicit是多余的.
struct A {
/* explicit is redundant. */
explicit A();
};
Run Code Online (Sandbox Code Playgroud)
在一些C++ 0x草案中(我相信它是n3035),它通过以下方式产生了不同:
A a = {}; /* error! */
A b{}; /* alright */
void function(A a);
void f() { function({}); /* error! */ }
Run Code Online (Sandbox Code Playgroud)
但是在FCD中,他们改变了这一点(尽管我怀疑他们并没有考虑到这个特殊原因),因为所有三种情况都对相应的对象进行了初始化.值初始化不会执行重载决策,因此在显式构造函数上不会失败.
除非另有明确说明,否则以下所有标准参考均指N4659:2017 年 3 月 post-Kona 工作草案/C++17 DIS。
\n(这个答案特别关注没有参数的显式默认构造函数)
\n{}非聚合的空复制列表初始化禁止使用显式默认构造函数受[over.match.list]/1 [重点是我的] 管辖:
\n\n\n当非聚合类类型的对象
\nT进行列表初始化时,\n[dcl.init.list] 指定执行重载解析,\n根据本节中的规则,重载解析会分两个阶段选择\n构造函数:\n
\n- (1.1)最初,候选函数是类的初始化器列表构造函数 ([dcl.init.list])
\nT,参数列表\n由初始化器列表作为单个参数组成。- (1.2)如果找不到可行的初始值设定项列表构造函数,则再次执行重载决策,其中候选函数都是类的构造函数
\nT,参数列表由初始值设定项列表的元素组成。如果初始值设定项列表没有元素并且
\nT有默认构造函数,则忽略第一阶段。在复制列表初始化中,如果explicit选择了构造函数,则初始化的格式不正确。[\xe2\x80\x89注意:这与其他情况([over.match.ctor]、[over.match.copy])不同,在其他情况下,仅考虑转换构造函数进行复制初始化。仅当此初始化是重载决策的最终结果的一部分时,此限制才适用。\xe2\x80\x89\xe2\x80\x94\xe2\x80\x89尾注\xe2\x80\x89]
对于非聚合使用空的花括号初始化列表的复制列表初始化禁止使用显式默认构造函数; {}例如:
struct Foo {\n virtual void notAnAggregate() const {};\n explicit Foo() {}\n};\n\nvoid foo(Foo) {}\n\nint main() {\n Foo f1{}; // OK: direct-list-initialization\n\n // Error: converting to \'Foo\' from initializer\n // list would use explicit constructor \'Foo::Foo()\'\n Foo f2 = {};\n foo({});\n}\nRun Code Online (Sandbox Code Playgroud)\n尽管上面的标准引用是指 C++17,但这同样适用于 C++11、C++14 和 C++20。
\nexplicit不是聚合[dcl.init.aggr]/1添加在 C++14 和 C++17 之间进行了一些更新,主要是允许聚合从基类公开派生,有一些限制,但也禁止explicit聚合的构造函数 [重点是我的]:
\n\n聚合是一个数组或一个类
\n\n
\n- (1.1)没有用户提供的、
\nexplicit或继承的构造函数([class.ctor]),- (1.2) 没有私有或受保护的非静态数据成员(子句 [class.access]),
\n- (1.3) 没有虚函数,并且
\n- (1.4) 没有虚拟、私有或受保护的基类 ([class.mi])。
\n
从C++20 实现的P1008R1(禁止使用用户声明的构造函数进行聚合)开始,我们可能不再声明聚合的构造函数。然而,仅在 C++17 中,我们就有一个特殊的规则,即用户声明的(但不是用户提供的)构造函数是否被显式标记决定了类类型是否是聚合。例如类类型
\nstruct Foo {\n Foo() = default;\n};\n\nstruct Bar {\n explicit Bar() = default;\n};\nRun Code Online (Sandbox Code Playgroud)\n在 C++11 到 C++20 中是聚合/非聚合,如下所示:
\nFoo&Bar都是聚合Foo&Bar都是聚合Foo一个聚合(Bar有一个explicit构造函数)Foo或Bar不是聚合(两者都有用户声明的构造函数)