我最近注意到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没有,则初始化是错误的access default constructor);
在这两种情况下,标准都要求调用默认构造函数。但是如果默认构造函数是非显式的,那就会发生这种情况。为了完整起见:
8.5.11 [decl.init]
如果没有为对象指定初始化程序,则该对象是默认初始化的;
据我所知,这只是从没有数据的转换。这没有意义。我能想到的最好的是:
void function(Class c);
int main() {
function(); //implicitly convert from no parameter to a single parameter
}
但显然这不是C ++处理默认参数的方式。还有什么能使explicit Class();
的行为与Class();
不同?
生成此问题的具体示例是std::function
[20.8.14.2 func.wrap.func]。它需要几个转换构造函数,其中没有一个被标记为显式,但默认构造函数是。
答案 0 :(得分:28)
这声明了一个显式的默认构造函数:
struct A {
explicit A(int a1 = 0);
};
A a = 0; /* not allowed */
A b; /* allowed */
A c(0); /* allowed */
如果没有参数,如下例所示,explicit
是多余的。
struct A {
/* explicit is redundant. */
explicit A();
};
在一些C ++ 0x草案中(我相信它是n3035),它在以下方面有所不同:
A a = {}; /* error! */
A b{}; /* alright */
void function(A a);
void f() { function({}); /* error! */ }
但是在FCD中,他们changed this(虽然,我怀疑他们没有考虑到这个特殊原因)因为所有三种情况 value-initialize 各自的对象。值初始化不会执行重载决策,因此在显式构造函数上不会失败。
答案 1 :(得分:1)
除非另有明确说明,否则以下所有标准参考文献均指N4659: March 2017 post-Kona working draft/C++17 DIS。
(此答案专门针对没有参数的显式默认构造函数)
{}
复制列表初始化禁止使用显式的默认构造函数受[over.match.list]/1的约束[强调我的]:
当非聚合类类型为
T
的对象被列表初始化时,例如 [dcl.init.list]指定执行重载解析 根据本节中的规则,重载分辨率选择 构造函数分为两个阶段:
- (1.1)最初,候选函数是类
T
和参数列表的初始化器列表构造函数([dcl.init.list])。 由初始化程序列表作为单个参数组成。- (1.2)如果找不到可行的initializer-list构造函数,则再次执行重载解析,其中所有候选函数 类
T
的构造函数和参数列表包括 初始化程序列表的元素。如果初始化列表中没有元素,并且
T
具有默认值 构造函数,省略第一阶段。 在 复制列表初始化,如果选择了explicit
构造函数, [注意:这与其他方法不同 情况([over.match.ctor],[over.match.copy]),仅 转换构造函数被考虑用于复制初始化。这个 限制仅在此初始化是最终的一部分时适用 解决过载的结果。 — 尾注]
复制列表初始化(用于非聚集)的空括号初始化列表 {}
禁止使用显式默认构造函数;例如:
struct Foo {
virtual void notAnAggregate() const {};
explicit Foo() {}
};
void foo(Foo) {}
int main() {
Foo f1{}; // OK: direct-list-initialization
// Error: converting to 'Foo' from initializer
// list would use explicit constructor 'Foo::Foo()'
Foo f2 = {};
foo({});
}
尽管上面的标准引用是针对C ++ 17的,但这同样适用于C ++ 11,C ++ 14和C ++ 20。
explicit
的类类型不是集合 [dcl.init.aggr]/1在C ++ 14和C ++ 17之间进行了一些更新,主要是通过允许聚合从基类公开派生而来,但有一些限制,但也禁止了explicit
构造函数用于聚集[重点我的]:
聚集是具有
的数组或类
- (1.1)没有用户提供的
explicit
或继承的构造函数([class.ctor]),- (1.2)没有私有或受保护的非静态数据成员(条款[class.access]),
- (1.3)没有虚拟功能,并且
- (1.4)没有虚拟,私有或受保护的基类([class.mi])。
自P1008R1(禁止使用用户声明的构造函数的聚合)(已针对C ++ 20实现)以来,我们可能再也不会声明聚合的构造函数。但是,仅在C ++ 17中,我们就有一条特殊的规则,即是否将用户声明的(但不是用户提供的)构造函数标记为显式,从而决定类类型是否为聚合类型。例如。班级类型
struct Foo {
Foo() = default;
};
struct Bar {
explicit Bar() = default;
};
在C ++ 11到C ++ 20中汇总/未汇总如下:
Foo
和Bar
都是聚合Foo
和Bar
都是聚合Foo
是聚合(Bar
具有explicit
构造函数)Foo
或Bar
都不是聚合(都具有用户声明的构造函数)