【问题标题】:Writing a Default Constructor Forces Zero-Initialization?编写默认构造函数强制零初始化?
【发布时间】:2014-12-16 13:16:06
【问题描述】:

这些是我的类定义:

class Foo{
    int _ent;
public:
    void printEnt() const{cout << _ent << ' ';}
};

class Bar{
    Foo _foo;
public:
    void printEnt() const{_foo.printEnt();}
};

这是我的测试代码:

char* buf = new char[sizeof(Foo) + sizeof(Foo) + sizeof(Bar)];

fill(buf, buf + sizeof(Foo) + sizeof(Foo) + sizeof(Bar), 'J');

cout << ((int*)buf)[0] << ' ' << ((int*)buf)[1] << ' ' << ((int*)buf)[2] << endl;

Foo* first = new (buf) Foo;
Foo* second = new (buf + sizeof(Foo)) Foo();
Bar* third = new (buf + sizeof(Foo) * 2) Bar;

first->printEnt(); second->printEnt(); third->printEnt();

我的输出是:

1246382666 1246382666 1246382666
1246382666 0 1246382666

但如果我将public 默认ctor 添加到FooFoo() : _ent(0) {}

我的输出变成:

1246382666 1246382666 1246382666
0 0 0

这是正确的行为吗?添加我自己的默认 ctor 是否应该消除默认初始化的可能性?

如果重要的话,我会在 gcc 4.8.1 上运行此代码。结果应该是可靠的,因为我正在调试和断言:assert(sizeof(Foo) == sizeof(int) &amp;&amp; sizeof(Bar) == sizeof(int));

【问题讨论】:

  • 如果有人有兴趣,我开始尝试了解值初始化和默认初始化之间的区别,因此响应:stackoverflow.com/a/27443703/2642059
  • (sizeof(int) / sizeof(char)) 没有多大意义。 sizeof(char) 始终为一。您还会忽略对齐和填充。
  • 为什么不只是buf + sizeof(Foo)
  • @pmr 这不是真的。保证new char[](或new unsigned char[])返回的指针对于任何类型的对象都充分对齐。
  • @JonathanMee 我还没有听说过没有这样的编译器。如果该类具有虚函数,我所知道的 所有 编译器都会分配一个额外的指针(或者有时两个,如果有虚拟继承)。而且我知道的所有编译器都强制对齐,所以像struct { double d; char c; } 这样的东西的大小将大于sizeof(double) + sizeof(char)。 (当然,空类的大小仍然至少为 1。)

标签: c++ initialization default default-constructor


【解决方案1】:

一旦你为一个类型提供了一个构造函数,它就永远是 调用,用于默认初始化和值 初始化。这是语言的基本原则。 所以一旦你定义了Foo::Foo(),它就会在你任何时候被调用 构造一个Foo;如果有默认构造函数,它将是 调用,即使在默认初始化的情况下。所以 您看到的行为是正确的。

编辑:

第 8.5/7 节解释了默认初始化,特别是:

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

——如果 T 是(可能是 cv 限定的)类类型(第 9 条),则调用 T 的默认构造函数(12.1) [...]

在您的情况下,您可能还想看看 如果没有提供,编译器会生成一个默认构造函数, §12.1/4;特别是生成的默认构造函数 调用任何基类或成员的默认构造函数。

值初始化在 §8.5/8 中。基本上是默认的 初始化之前是零初始化,因此默认 什么都不做的初始化仍然找到一切 零初始化。

然而,更根本的是:在这种情况下,一个非常基本的 涉及 C++ 原理,早在第一个 标准:如果您为对象提供构造函数,它 使用。无需进行各种奇怪的指针转换,它 没有正确的对象是不可能得到的 建。该标准描述了这是如何发生的,并涵盖 还有很多其他的特殊情况,但基本原理是 从一开始就在那里(以及任何会导致它不 在标准中得到尊重必然失败)。

【讨论】:

  • 这似乎是正确的答案,你能添加一个来源吗? @juanchopanza 链接:en.cppreference.com/w/cpp/language/value_initialization 但它只解决:“这是使用空初始化程序构造变量时执行的初始化。”我希望看到一些说明默认初始化将调用用户默认 ctor 的内容。
  • 我没有发现值初始化是“基本上默认初始化之前为零初始化”这一事实的参考。我发现,如果一个类没有用户声明的默认 c-tor,您可以使用 (){} 进行零初始化,但如果该类声明了默认 c-tor,那么该 c-tor 是在没有事先调零的情况下调用。对此的推论是,如果您声明一个默认的 c-tor,您必须处理它内部的所有归零。事实上,在将成员的子集设置为某些选择值之前,我发现没有简单的方法(即,memset() 以外的方法)对类进行零初始化。
【解决方案2】:

您的问题已在标准的 C++11 修订版中得到解答:

class Foo{
    int _ent=0;
public:

    // ...
};

如果您随后定义了自己的默认构造函数,该成员仍将被初始化为其默认值,即使您的默认构造函数没有明确这样做。

【讨论】:

  • 但是在我的默认ctor之前,为什么new Foo没有将_ent初始化为0,而new Foo()却将_ent初始化为0?就好像编译器生成的默认构造函数有一些魔力,可以知道我是否明确调用了它。
  • @JonathanMee 因为 C++ 语言指定了这种行为。 new Foo() 值初始化 Foo 对象。 new Foo 默认初始化它。
  • @JonathanMee 编译器生成的默认构造函数不会初始化像 int 这样的原始数据类型。它的价值真的可以是任何东西。
  • @juanchopanza 我认为这就是我的问题。 为什么在我定义了自己的默认 ctor 后,默认初始化不再发生?
  • @JonathanMee 确实如此。它的作用是调用用户定义的默认构造函数。
【解决方案3】:

当您提供默认构造函数时,您将不再获得编译器生成的构造函数。由于您的默认构造函数将成员初始化为 0,因此成员将始终为 0,尤其如此,因为该成员是私有的并且您无法更改它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 2021-12-16
    • 2015-07-10
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多