《深入探讨C++对象模型》笔记 二

构造函数语意学

1,默认构造函数(default constructors)是在需要的时候被编译器产生出来。这里需要理解的是被谁需要,产生出来做什么事情。分析下面这段代码

 9 class foo
10 {
11 public:
12     int m_nval;
13     foo *pnext;
14 };
15 
16 void foo_bar()
17 {
18     foo bar;
19     if (bar.m_nval 
20         || bar.pnext != NULL)
21     {
22         // do something
23         cout<<"do something..."<<endl;
24     }
25 }

  从上面的例子中,可以看出foo_bar()函数中定义了一个foo类对象bar,当这个对象bar初始化后的两个成员变量被赋值时输出一句话。上面这段代码编译器会产生出一个默认构造函数(default constructors)吗?答案是不会。原因在于一个是程序的需要,一个是编译器的需要。程序的需要是通过程序员来编写构造函数的,而编译器的需要是通过编译器默认的产生出来的。

  只有4种情况,编译器才会产生默认构造函数,这种构造函数被视为是一种nontrivial default constructors(有用的默认构造函数)。这4种情况分别是:

1,“带有default constructor”的Member class object。

2,“带有default constructor”的base class

3,“带有virtual function”的class

4,“带有一个virtual base class”的class

下面分别介绍这几种情况

1,“带有default constructor”的Member class object,例子:

 1 class Foo
 2 {
 3 public:
 4     Foo(){};
 5     Foo(int nval){ m_nval = nval;};
 6 public:
 7     int m_nval;
 8 };
 9 
10 class Bar
11 {
12 public:
13     Foo foo; // 带有default constructor”的Member class object
14     char *str;
15 };
16 
17 void foo_bar()
18 {
19     Bar _bar;//Bar::foo函数成员对象 必须在此处合成出来
20     if (_bar.str != NULL)
21     {
22         cout<<"do something..."<<endl;
23     }
24 }

上面这段代码,编译器为class Bar合成一个default constructor, 被合成出来的default constructor函数体内含有必要的代码能够调用class Foo的默认构造函数

Foo(){};来处理Bar中的Bar::foo,但是这个default constructor不产生任何的代码来初始化Bar::str成员函数,因为将Bar::foo初始化是编译器的责任,将Bar::str初始化则是程序员的责任。
  被编译器后台合成出来的default constructor应该类似于:
class Bar
{
public:
    Foo foo;
    char *str;
public:
    inline Bar();
};

inline Bar::Bar()
{
    foo.Foo::Foo();//伪代码
}

   再次强调,被编译器后来合成出来的构造函数只是用来满足编译器的需要,而不是程序的需要。

  为了能够让上面程序正确的执行,字符指针*str也需要被初始化,这需要程序员提供一个构造函数来对这个指针进行初始化,初始化构造函数如下:

Bar::Bar()
{
    str = "构造实例";
}

  现在程序的需求满足了,但是编译器还需要初始化Bar::foo对象,由于默认构造函数已经明确的被定义出来了,编译已不能合成默认的构造函数,这个时候编译器会采取这种方式“如果class A 内含有一个或一个以上的member class object,那么class A的每一个constructor必须调用每一个member class object的default”。这时编译器会扩张已存在的构造函数,在其中安插一些代码,使得user code在被执行之前,先调用必要的default constructor.。扩张后的代码类似于:

Bar::Bar()
{
    foo.Foo::Foo();//编译器扩张的代码,伪代码
    str = "构造实例";
}

  对于含有多个memeber class object。C++语言要求按照这些成员类对象声明的次序来调用各个的constructor。

2,“带有default constructor”的base class

  类似的道理,如果一个构造函数的类派生自一个带有默认构造函数的类,那么这个派生类在创建对象的时候,编译器隐含的合成一个默认构造函数。原理同1类似。

3,“带有virtual function”的class

  另有两种情况也需要编译器合成默认构造函数,1:class声明或者继承了一个虚函数,2.class派生自一个继承串链,其中有一个或者多个虚基类。

不管哪一种情况,由于没有人为的声明一个构造函数,那么编译器需要自己合成一个默认的构造函数。

4,“带有一个virtual base class”的class

  上面四种情况,会使编译器必须为一个未 人为声明构造函数的类 去合成一个默认的构造函数,被合成出来的默认构造函数只能满足编译器自己的需要。对于没有存在这四种情况并且又没有人为的声明构造函数的类,他们的默认构造函数不会被合成出来。

  常见的两种误解:

  1,任何class如果没有定义构造函数,就会被合成出来一个

  2,编译器合成出来的默认构造函数会明确的设定class内的每一个数据成员的默认值。

  这两种想法都是都是错误的