【问题标题】:How do I make user defined empty default constructor behave like compiler defined empty constructor如何使用户定义的空默认构造函数表现得像编译器定义的空构造函数
【发布时间】:2019-09-01 06:29:35
【问题描述】:

我已经尝试学习 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. 

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

【问题讨论】:

  • 我认为 A a2{}; 正在进行成员初始化。
  • @user4581301 必须是 C++11 或更高版本,否则 A a2{};B b2{}; 无法编译,C++98/03 只接受 A a2 = {} 的形式。你应该把它作为答案。
  • 我还是不明白 - cppreference 明确表示默认构造函数是空的,它的初始化列表也是空的 - 那么如果编译器提供的构造函数为空且为空,该成员如何初始化为零初始化列表?
  • 重复的Is it guaranteed that defaulted constructor initialize built in types automatically to 0? 很接近,但不是很好的重复。两者都利用了Zero initialization,但骗子没有解释问题的关键:为什么A 有效而B 无效。我已取消保留。
  • 好的,大家:A不是聚合(因为A::m是私有的),所以不能聚合初始化。

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


【解决方案1】:

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

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

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

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

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

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

相比之下,如果你在默认构造函数中创建一个空的主体,你说的是完全不同的东西。您明确地说,“如果用户调用我的默认构造函数,我希望我的成员被默认初始化。”毕竟,当您在构造函数中有一个空的成员初始值设定项列表时,这就是它的含义。所以这就是它应该做的。

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

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


如果你想更深入地理解值初始化的思想,可以考虑这个。

int i{};

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

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

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

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

【讨论】:

  • 这是不正确的,list-initialization 只会在类型有default 构造函数。
  • @Tzalumen:是的,两种类型都有默认构造函数。
  • 不,你误会了,列表初始化专门寻找default。如果它看到一个空的 c'tor,它将不会进行值初始化。 A 有一个default c'tor,B 有一个空的 c'tor,A 将在列表初始化期间初始化值,B 不会。
  • @Tzalumen:我不知道您所说的“default c'tor”与“空 c'tor”不同是什么意思。 standard makes no such distinction.list initialization only says "is a class type with a default constructor" 或许你可以用标准的措辞来解释。
  • 实现一个空的构造函数体会导致编译差异,特别是缺少定义的复制 c'tor,这会导致某些 STL 操作出现特殊情况的编译错误。
【解决方案2】:

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

以下情况会进行零初始化:

...

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

还有here

值初始化的效果是:

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

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

【讨论】:

  • 最后,一个解释观察到的行为的答案。
猜你喜欢
  • 1970-01-01
  • 2014-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多