C++ 显式默认构造函数的用途

C++ 显式默认构造函数的用途,c++,default-constructor,explicit,explicit-constructor,C++,Default Constructor,Explicit,Explicit Constructor,我最近注意到C++0x中有一个类调用显式默认构造函数。然而,我没有想到一个可以隐式调用默认构造函数的场景。它似乎是一个毫无意义的说明符。我想它可能会禁止c类支持类别c=Class()但情况似乎并非如此 C++0x FCD中的一些相关引用,因为我更容易导航[类似的文本存在于C++03中,如果不在相同的位置] 12.3.1.3[类别转换] 默认构造函数可以是显式构造函数;此类构造函数将用于执行默认初始化或值初始化(8.5) 它接着提供了一个显式默认构造函数的示例,但它只是模仿了我上面提供的示例 8.

我最近注意到C++0x中有一个类调用显式默认构造函数。然而,我没有想到一个可以隐式调用默认构造函数的场景。它似乎是一个毫无意义的说明符。我想它可能会禁止c类支持
类别c=Class()但情况似乎并非如此

C++0x FCD中的一些相关引用,因为我更容易导航[类似的文本存在于C++03中,如果不在相同的位置]

12.3.1.3[类别转换]

默认构造函数可以是显式构造函数;此类构造函数将用于执行默认初始化或值初始化(8.5)

它接着提供了一个显式默认构造函数的示例,但它只是模仿了我上面提供的示例

8.5.6[起始日期]

默认初始化T类型的对象意味着:

-如果T是(可能是cv限定的)类类型(第9条),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的)

8.5.7[起始日期]

初始化T类型对象的值意味着:

-如果T是一个(可能是cv限定的)类类型(第9条),具有用户提供的构造函数(12.1),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的)

在这两种情况下,标准都会调用默认构造函数。但如果默认构造函数是非显式的,就会发生这种情况。为了完整性起见:

8.5.11[起始日期]

如果没有为对象指定初始值设定项,则该对象默认为已初始化

据我所知,这只是没有数据的转换。这没有道理。我能想到的最好办法是:

void function(Class c);
int main() {
  function(); //implicitly convert from no parameter to a single parameter
}
<>但是显然这不是C++处理默认参数的方式。还有什么东西可以使
显式类()的行为与
Class()不同


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

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

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中,它们(尽管,我怀疑它们没有考虑到这个特殊的原因)在这三种情况下都初始化各自的对象。值初始化不会进行重载解析,因此不会在显式构造函数上失败

除非另有明确说明,以下所有标准参考文件均指


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


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

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

  • (1.1)最初,候选函数是类
    T
    和参数列表的初始值设定项列表构造函数([dcl.init.list]) 由初始值设定项列表作为单个参数组成
  • (1.2)如果未找到可行的初始值设定项列表构造函数,则再次执行重载解析,其中候选函数均为 类
    T
    的构造函数和参数列表由 初始值设定项列表的元素
如果初始值设定项列表没有元素且
T
具有默认值 在构造函数中,省略了第一阶段In 复制列表初始化,如果选择了显式构造函数,则 初始化格式不正确。[ 注意:这与其他方法不同 情况([over.match.ctor],[over.match.copy]),其中 复制初始化时会考虑转换构造函数 仅当此初始化是最终初始化的一部分时,此限制才适用 过载解析的结果。 — 尾注 ]

对于非聚合,使用空括号init list进行复制列表初始化
{}
禁止使用显式默认构造函数;例如:

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
的用户声明构造函数的类类型不是聚合 added在C++14和C++17之间进行了一些更新,主要是允许聚合从基类公开派生,但有一些限制,但也禁止聚合的
显式
构造函数[emphasismine]:

聚合是具有

  • (1.1)未提供任何用户、
    显式
    或继承的构造函数([class.ctor])
  • (1.2)无私有或受保护的非静态数据成员(第条[类访问])
  • (1.3)无虚拟功能,以及
  • (1.4)没有虚拟、私有或受保护的基类([class.mi])
从(禁止使用用户声明的构造函数进行聚合)开始,我们可能不再为聚合声明构造函数。但是,仅在C++17中,我们就有一个特殊的规则,即用户是否声明(但不是用户提供的)构造函数被标记为显式,以决定类类型是否为聚合。例如,类类型

struct Foo {
    Foo() = default;
};

struct Bar {
    explicit Bar() = default;
};
在C++11 thr中是聚合/不是聚合