44

我最近注意到 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
}

但显然这不是 C++ 处理默认参数的方式。还有什么会使explicit Class();行为与 不同Class();

产生这个问题的具体例子是std::function[20.8.14.2 func.wrap.func]。它需要几个转换构造函数,其中没有一个被标记为显式,但默认构造函数是。

4

2 回答 2

35

这声明了一个显式的默认构造函数:

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 中,他们改变了这一点(虽然,我怀疑他们并没有考虑到这个特殊的原因),因为所有三种情况都对各自的对象进行了值初始化。值初始化不会进行重载解析舞蹈,因此不会在显式构造函数上失败。

于 2010-05-14T19:24:15.807 回答
5

除非另有明确说明,否则以下所有标准参考均指N4659:2017 年 3 月 Kona 工作草案/C++17 DIS 后


(这个答案特别关注没有参数的显式默认构造函数)


案例 #1 [ C++11 到 C++20 ]:{}非聚合的空复制列表初始化禁止使用显式默认构造函数

[over.match.list]/1 [强调我的] 管理:

当非聚合类类型的对象T被列表初始化,使得 [dcl.init.list] 指定根据本节中的规则执行重载解析时,重载解析分两个阶段选择构造函数:

  • (1.1)最初,候选函数是类的初始化器列表构造函数 ([dcl.init.list]),T参数列表由初始化器列表作为单个参数组成。
  • (1.2)如果没有找到可行的初始化列表构造函数,则再次执行重载决议,其中候选函数是类的所有构造函数,T参数列表由初始化列表的元素组成。

如果初始化列表没有元素并且T有默认构造函数,则省略第一阶段。在复制列表初始化中,如果explicit选择了构造函数,则初始化格式错误。[ <em>注意:这与其他情况([over.match.ctor],[over.match.copy])不同,其中仅考虑转换构造函数进行复制初始化。仅当此初始化是重载决议的最终结果的一部分时,此限制才适用。— <em>结束注释]

非聚合的带有空括号 初始化列表的复制列表初始化禁止使用显式默认构造函数;{}例如:

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。


案例 #2 [仅限 C++17 ]:具有用户声明的构造函数且标记为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])。

从为 C++20 实现的P1008R1禁止使用用户声明的构造函数进行聚合)开始,我们可能不再为聚合声明构造函数。然而,仅在 C++17 中,我们有一个特殊的规则,即用户声明的(但不是用户提供的)构造函数是否被标记为显式决定了类类型是否为聚合。例如类类型

struct Foo {
    Foo() = default;
};

struct Bar {
    explicit Bar() = default;
};

在 C++11 到 C++20 中是聚合/非聚合,如下所示:

  • C++11: Foo&Bar都是聚合
  • C++14: Foo&Bar都是聚合
  • C++17:只有Foo聚合(Barexplicit构造函数)
  • C ++ 20:都不FooBar聚合(两者都有用户声明的构造函数)
于 2020-09-23T18:02:30.230 回答