对用户定义的空构造函数如何初始化非静态非POD成员变量感到困惑

时间:2013-07-28 05:24:45

标签: c++ initialization default-constructor value-initialization

我知道非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

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

— 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 i,{{ 1}}以某种方式隐式调用。我想知道这是怎么发生的?

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

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

Test2()

此问题与c++ empty constructor and member initialization类似 但不同的是,我不想询问最终结果行为是什么,而是想知道为什么会出现最终结果行为。

3 个答案:

答案 0 :(得分:2)

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

  

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

     
      
  • 如果实体是具有大括号或等于初始值的非静态数据成员,则实体初始化   如8.5;
  • 中所述   
  • 否则,如果实体是变体成员(9.5),则不执行初始化;
  •   
  • 否则,实体默认初始化(8.5)。
  •   

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

另外,从第10段开始:

  

在非委托构造函数中,初始化按以下顺序进行:    - 首先,仅对于派生程度最高的类(1.8)的构造函数,虚拟基类按照它们出现在基类的有向无环图的深度优先从左到右遍历的顺序进行初始化,其中“左” -to-right“是派生类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;
};

答案 1 :(得分:2)

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

#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:“类型的对象也可以通过 braced-init-list 初始化。应用列表初始化语义;参见8.5和8.5.4。”
  • §8.5.4p3:“类型T的对象或引用的列表初始化定义如下:...如果初始化列表没有元素,T是类型类型,默认构造函数,该对象是值初始化的。“
  • §8.5p7:“要值初始化类型为T的对象意味着:......如果T是(可能是cv限定的)类类型(条款9)使用用户提供的构造函数(12.1),然后调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化格式不正确)

答案 2 :(得分:0)

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

  

如果某个给定的非静态数据成员或基类没有被mem-initializer-id命名(包括没有mem-initializer-list的情况,因为构造函数没有ctor-initializer),那么

     

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

     

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

换句话说,如果非POD类类型的对象没有出现在初始值设定项列表中,编译器会将其解释为,好像对象出现,其默认构造函数被调用。< / p>

另请注意,其他类型(即基元和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