如何使用户定义的空默认构造函数的行为类似于编译器定义的空构造函数

时间:2019-04-10 17:53:22

标签: c++ c++11 constructor initialization

我已经尝试学习C ++大约一个月了,但这仍然让我感到困惑。例如,以下“简单”代码:

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,编译器定义的默认构造函数的主体和初始化器列表均为空。因此,当显式定义具有空主体和空初始化列表的默认构造函数时,如何(在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. 

据我了解,两个构造函数的行为应完全相同。看起来很简单,但我不明白。

2 个答案:

答案 0 :(得分:2)

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

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

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

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

为什么规则这么说?因为= default不应 等同于空的构造函数主体。确实,与众不同是= default 存在作为特征的原因。

= default是您的默认构造函数时,您说的是“像往常一样生成默认构造函数”。这很重要,因为您可以做一些事情来主动阻止编译器为您生成默认构造函数。如果指定其他构造函数(不是复制/移动构造函数),则编译器将不会自动生成一个。因此,通过使用= default语法,您可以告诉编译器要生成的默认构造函数。

相反,如果您在默认构造函数中创建一个空主体,则表示完全不同。您明确地说,“如果用户调用我的默认构造函数,则我想要我的成员将被默认初始化。”毕竟,这就是当构造函数中有一个空的成员初始化程序列表时的意思。这就是它应该做的。

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

基本上,Cppreference的陈述是完全错误的;它没有“与具有空主体和空初始化列表的用户定义构造函数完全一样的效果”。也不应该。


如果您想进一步了解值初始化的想法,请考虑一下。

int i{};

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

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

应该为s.i生成0值。怎么发生的?因为值初始化将对s进行零初始化。

那么,用户如何说自己不想要或想要某种特殊形式的初始化?交流方式与交流其他方式相同:添加构造函数。具体来说,是一个默认的构造函数,它可以执行您想要的初始化形式。

答案 1 :(得分:2)

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

  

在以下情况下执行零初始化:

     

...

     

2)作为非类类型和没有构造函数的值初始化类类型的成员的值初始化序列的一部分,包括没有初始化器的聚合元素的值初始化提供。

here

  

值初始化的影响是:

     

1)如果T是具有至少一个用户提供的任何类型的构造函数的类类型,则将调用默认构造函数;

有道理。一个人应该能够控制是否可以添加任何其他操作。如果提供构造函数,则负责对象的初始化。