如何使用户定义的空默认构造函数表现得像编译器定义的空构造函数

How do I make user defined empty default constructor behave like compiler defined empty constructor

我已经尝试学习 C++ 大约一个月了,但它仍然让我感到困惑。例如这个 'easy' 代码:

class A
{
    int m;
public:
    A() = default;
    int getM() { return m; }
};

class B
{
    int m;
public:
    // empty body and empty initializer list
    B() {} // isn't this the same as "B() = default;" ?
    int getM() { return m; }

};

int main()
{
    A a1;
    A a2{};

    std::cout << "a1.m=" << a1.getM() << "\n";
    std::cout << "a2.m=" << a2.getM() << "\n";

    B b1;
    B b2{};

    std::cout << "b1.m=" << b1.getM() << "\n";
    std::cout << "b2.m=" << b2.getM() << "\n";

    std::cin.ignore();
}

结果:

a1.m=...garbage
a2.m=0
b1.m=...garbage
b2.m=...garbage

根据 CPP REFERENCE 编译器定义的默认构造函数确实有空主体和空初始化列表。那么当显式定义的具有空主体和空初始化列表的默认构造函数没有时,它到底是如何(在 class A 中)将成员 'm' 初始化为零的。根据 cppreference 摘录:

If the implicitly-declared default constructor is not defined as deleted,  it is defined (that is, a function body is generated and compiled)
by the compiler if odr-used, and it has exactly the same effect as a user-defined constructor with empty body and empty initializer list. 

据我了解,两个构造函数的行为方式应该完全相同。看似简单,我还是不懂。

这是基本思路。 A a2{};B b2{}; 都会对这两个对象执行所谓的“value initialization”。但是,值初始化的行为方式取决于这些类型的定义方式。

B 是一个具有用户提供的默认构造函数的对象。 "User-provided" 是为默认构造函数提供主体时的术语。因此,值初始化将调用默认构造函数。该默认构造函数不初始化其成员,因此成员保持未初始化状态。

A 是一个没有用户提供的默认构造函数的对象。它也没有任何其他用户提供的构造函数。并且默认构造函数也没有被删除。 A 中没有默认的成员初始值设定项。考虑到所有这些,值初始化将对对象执行零初始化。这意味着它将在该对象存在之前将所有零写入该对象的内存。

规则就是这么说的;两者的行为不同,也无意如此。在 所有 情况下,您也无法使用户提供的默认构造函数像默认的默认构造函数一样工作。您可以让用户提供的构造函数值初始化其成员,但它会一直这样做,即使您使用默认初始化(例如 B b1;)也是如此。

规则为什么这么说?因为 = default 不应该 等同于一个空的构造函数体。事实上,与众不同是 = default 存在 的原因。

当您 = default 默认构造函数时,您说的是 "generate the default constructor as you normally would"。这很重要,因为您可以采取一些措施主动阻止编译器为您生成默认构造函数。如果指定其他构造函数(不是 copy/move 构造函数),编译器将不会自动生成一个。所以通过使用 = default 语法,你告诉编译器你想要生成的默认构造函数。

相比之下,如果您在默认构造函数中创建一个空主体,则您说的是完全不同的东西。你明确地说,"If a user calls my default constructor, I want my members to be default-initialized." 毕竟,当你在构造函数中有一个空的成员初始值设定项列表时,这就是它的意思。这就是它应该做的。

如果 = default 和空主体的行为相同,您将无法获得该行为,也就是说无论如何您都希望对成员进行默认初始化。

基本上,Cppreference的说法是完全错误的;它没有 "exactly the same effect as a user-defined constructor with empty body and empty initializer list"。也不应该。


如果您想进一步了解值初始化的思想,请考虑这个。

int i{};

保证为 i 生成 0 值。因此,这是合理的:

struct S{int i;};
S s{};

也应该s.i生成值0。这是怎么发生的?因为值初始化会零初始化 s.

那么用户怎么说他们不想要那个,或者想要某种特殊形式的初始化呢?你传达它的方式与你传达其他一切的方式相同:你添加一个构造函数。具体来说,默认构造函数执行您想要的初始化形式。

如果提供了任何构造函数,则不会按规定进行零初始化 here

Zero initialization is performed in the following situations:

...

2) As part of value-initialization sequence for non-class types and for members of value-initialized class types that have no constructors, including value initialization of elements of aggregates for which no initializers are provided.

还有here

The effects of value initialization are:

1) if T is a class type with at least one user-provided constructor of any kind, the default constructor is called;

这是有道理的。人们应该能够控制是否可以添加任何额外的操作。如果您提供构造函数,您将负责对象的初始化。