2

我知道非 POD 类型的默认初始化也会通过调用它们的默认构造函数来默认初始化非静态非 POD 成员变量。但我不确定这是如何发生的。这是我的意思的一个例子:

#include <iostream>
#include <vector>
using namespace std;

class Test2 {
  public:
    Test2() {cout <<"Here";}
};

class Test {
  public:
    Test() {}
    Test2 i;
};

int main() {
  Test foo;
}

输出是:

Here

基于初始化器的 C++ 标准 (8.5),对于默认初始化:

— if T is a non-POD class type (clause 9), the default constructor
for T is called (and the initialization is ill-formed if T has no
accessible default constructor);

因此,鉴于此,我确实希望默认构造函数Test()会被调用,但是我的空类默认构造函数尚未明确Test初始化,以某种方式被隐式调用。我想知道这是怎么发生的?Test2 iTest2()

同样,对于值初始化(与上面的示例无关),如果一个空的用户定义的默认构造函数没有显式地将 POD 非静态成员变量初始化为零,那么该变量如何初始化为零(我知道它确实这样做)?由于基于标准,似乎对于值初始化,当您拥有用户定义的默认构造函数时发生的所有事情就是构造函数被调用。

值初始化的 C++ 标准的相应部分如下:

— if T is a class type (clause 9) with a user-declared constructor (12.1), then the   
default constructor for T is called (and the initialization is ill-formed if T has no 
accessible default constructor);

这个问题类似于c++ 空构造函数和成员初始化 ,但不同的是,我不是问最终结果行为是什么,而是想知道为什么会发生最终结果行为。

4

3 回答 3

2

如果非静态 POD 成员未在构造函数中显式初始化,则具有用户定义的默认构造函数的类型的值初始化不会对它们执行初始化。例如,在这个程序中:

#include <iostream>

using namespace std;

struct Foo {
    // Foo has a user-defined default constructor
    // that does not initialize i
    Foo() {}
    int i;
};

int main() {
    Foo x{}; // value-initialize x
    cout << x.i << endl;
}

x.i未初始化。因此,该程序在技术上具有未定义的行为,但在这种情况下,“未定义行为”很可能意味着它将打印一个可能不是 0 的未指定整数值。

语言律师论证:

  • §12.6.1p2:“类类型的对象也可以通过花括号初始化列表初始化。列表初始化语义适用;参见 8.5 和 8.5.4。”
  • §8.5.4p3:“对象或类型引用的列表初始化T定义如下:......如果初始化列表没有元素并且T是具有默认构造函数的类类型,则该对象是值初始化的。”
  • §8.5p7:“对类型对象进行值初始化T意味着:......如果T是(可能是 cv 限定的)类类型(第 9 条),带有用户提供的构造函数(12.1),则T调用默认构造函数(如果T没有可访问的默认构造函数,则初始化格式错误)
于 2013-07-28T18:28:41.427 回答
2

在 C++11 标准中,第 12.6 节第 8 段:

在非委托构造函数中,如果给定的非静态数据成员或基类不是由 mem-initializer-id 指定的(包括由于构造函数没有 ctor-initializer 而没有 mem-initializer-list 的情况)并且实体不是抽象类(10.4)的虚拟基类,则

  • 如果实体是具有大括号或等号初始化器的非静态数据成员,则实体按照 8.5 中的规定进行初始化;
  • 否则,如果实体是变体成员(9.5),则不执行初始化;
  • 否则,实体被默认初始化(8.5)。

您遇到第三种情况,其中成员没有初始化程序,并且该成员不是变体成员,因此在这种情况下它是默认初始化的。

此外,从第 10 段开始:

在非委托构造函数中,初始化按以下顺序进行: - 首先,并且仅对于最派生类 (1.8) 的构造函数,虚拟基类按照它们出现在深度优先左到 - 的顺序进行初始化基类的有向无环图的右遍历,其中“从左到右”是派生类 base-specifier-list 中基类的出现顺序。

  • 然后,直接基类按照它们出现在 base-specifier-list 中的声明顺序进行初始化(无论 mem-initializers 的顺序如何)。
  • 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样不管 mem-initializers 的顺序)。
  • 最后,执行构造函数主体的复合语句。

无论您在构造函数中指定什么,成员都将在构造函数主体执行之前被初始化。

mem-initializer-id 是用于引用构造函数初始化列表中的成员的标识符:

class Test {
  public:
    Test() : i() {} // Here `i` is a mem-initializer-id
    Test2 i;
};
于 2013-07-28T05:50:34.583 回答
0

根据http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1905.pdf中的 C++ 标准草案,第 12.6.2 节:

如果给定的非静态数据成员或基类不是由 mem-initializer-id 命名的(包括由于构造函数没有 ctor-initializer 而没有 mem-initializer-list 的情况),则

— 如果实体是(可能是 cv 限定的)类类型(或其数组)或基类的非静态数据成员,并且实体类是非 POD 类,则实体是默认初始化的(8.5) . 如果实体是 const 限定类型的非静态数据成员,则实体类应具有用户声明的默认构造函数。

— 否则,实体未初始化。如果实体是 const 限定类型或引用类型,或者是(可能是 cv 限定的)POD 类类型(或其数组),包含(直接或间接)const 限定类型的成员,则程序是错误的形成。

换句话说,如果一个非 POD 类类型的对象没有出现在初始化列表中,编译器会将此解释为该对象已经出现并调用了其默认构造函数。

另外,请注意其他类型(即原语和 POD 类型)未初始化,这与您在问题中指出的不同。全局对象是零初始化的,但堆栈上的对象并非如此。这是我整理的一个小程序来测试它:

#include <iostream>

class T
{
public:
    T() {}

    void put(std::ostream &out)
    {
        out << "a = " << a << std::endl;
        out << "b = " << b << std::endl;
        out << "c = " << c << std::endl;
    }
private:
    int a;
    int b;
    int c;
};

T t2;

int main()
{
    T t;
    t.put(std::cout);
    t2.put(std::cout);

    return 0;
}

使用 g++ 4.5.2 编译,我得到以下输出:

a = 8601256
b = 3
c = 2130567168
a = 0
b = 0
c = 0
于 2013-07-28T05:51:53.623 回答