首页 > 代码库 > C++ 对象模型--- 默认构造函数

C++ 对象模型--- 默认构造函数

“default constructor ... 在需要的时候编译器产生出来。“  ------ 《深度探索C++对象模型》,P39(华中科技大学出版)

-------------------------------------------------------------------------------------------------------------------------------------------------------

C++ 新手一般有两个常见的误解

  1. 任何 class 如果没有被定义 default constructor,就会被合成出来一个;
  2. 编译器合成出来的 default constructor 会明确设定 “class 内 每一个 data member 的 默认值”.

如你所见,没有一个是真的.

--------------------------------------------------------------------------------------------------------------------------------------------------------

在 以下 4 种情况下,编译器会 合成 构造函数

  • case 1 当一个class 中 “带有 default constructor”的 member class object;
  • case 2 当一个class 派生自 ”带有 default constructor“的 base class;
  • case 3 当一个class 声明(或 继承)一个 virtual function;
  • case 4 当一个class 派生自一个 继承串链,其中有一个 或 多个 virtual base class.

        以上 4 种情况,会导致 “编译器 必须 为 未声明 constructor 的 classes 合成 一个 default constructor”.  C++ Standard [ISO-C++95](不知现在 C++ standard 对此处的 描述 是否 有所变化) 把 那些合成物 称为 implicit nontrivial default constructor. 被合成出来的 constructor 只能满足编译器(而非程序)的需求. 它之所以能完成任务,是借着“调用 member object 或 base class 的 default constructor” 或是 “为每一个object 初始化 其 virtual function 机制 或 virtual base class 机制” 而完成的. 对于不存在 这 4 种情况 而又没有声明 任何 constructor 的 classes,我们说 它们拥有的是 implicit trivial default constructor,它们实际上并不会被合成出来.

        在合成的 default constructor 中,只有 base class subobjects 和 member class objects 会被初始化,所有其他的 nonstatic data member,如整数,指针,整型数组等等都不会被初始化. 如果程序需要一个“把某指针设为 0 ”的 default constructor,那么提供它的应该是程序员.

 

测试环境: CentOS 6.5, g++ 4.4.7

 

下面通过 查看 反汇编代码(使用 objdump -d xxx.o 查看) 来确定 是否 有 default constructor 被合成

===================================================================================================================

1.  不是 上述 四种情况,也无用户定义的 constructor

 1 #include <iostream>
 2 
 3 class TEST_A                 // 不是 上述 四种情况,也无用户定义的 constructor
 4 {
 5     public:
 6         int a;
 7 };
 8 
 9 int main()
10 {
11     TEST_A test_a;
12     return 0;
13 }

反汇编: 可以看到 下面 汇编代码 中 并没有 call,即没有 函数调用,也就表明 在 定义 一个 TEST_A 对象时,没有 调用 constructor,说明 编译器 并没有为它合成 default constructor.

1 00000000 <main>:
2    0:    55                       push   %ebp
3    1:    89 e5                    mov    %esp,%ebp
4    3:    83 ec 10                 sub    $0x10,%esp
5    6:    b8 00 00 00 00           mov    $0x0,%eax
6    b:    c9                       leave  
7    c:    c3                       ret 

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 1 #include <iostream>
 2 
 3 class TEST_A                 // 不是 上述 四种情况,也无用户定义的 constructor
 4 {
 5     public:
 6         int a;
 7 };
 8 class TEST_B:public TEST_A   // 不是 上述 四种情况,也无用户定义的 constructor,TEST_B 继承自 TEST_A
 9 {
10     public:
11         int a;
12 };
13 
14 int main()
15 {
16     TEST_B test_b;
17     return 0;
18 }

反汇编:TEST_B  虽然继承于 TEST_A(不是 虚拟继承),但是 TEST_A 并没有 default constructor,也不符合 上述 4 种情况,TEST_B 自身又无 member

object,也没有 virtual function,故 编译器 不会为它 合成 default constructor. 下面的 汇编代码中没有 函数调用,也证明了这点.

1 00000000 <main>:
2    0:    55                       push   %ebp
3    1:    89 e5                    mov    %esp,%ebp
4    3:    83 ec 10                 sub    $0x10,%esp
5    6:    b8 00 00 00 00           mov    $0x0,%eax
6    b:    c9                       leave  
7    c:    c3                       ret

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

2. 不是上述 4种情况,有用户自定义 default constructor,或许 还有 自定义 destructor

 1 #include <iostream>
 2 
 3 class TEST_A                 // 不是 上述 四种情况,有用户定义的 default constructor
 4 {
 5     public:
 6         TEST_A(){}
 7         int a;
 8 };
 9 
10 int main()
11 {
12     TEST_A test_a;
13     return 0;
14 }

反汇编:有用户自定义的 default constructor,定义一个类对象时,自然 会 调用 default constructor.  没有调用 destructor:用户未定义,也不符合 编译器 合成

destructor 的 条件,故 没有 destructor 供其调用(也没有必要)

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    83 ec 20                 sub    $0x20,%esp
 6    9:    8d 44 24 1c              lea    0x1c(%esp),%eax
 7    d:    89 04 24                 mov    %eax,(%esp)
 8   10:    e8 fc ff ff ff           call   11 <main+0x11>   //调用 用户自定义 default constructor
 9   15:    b8 00 00 00 00           mov    $0x0,%eax
10   1a:    c9                       leave  
11   1b:    c3                       ret

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 1 #include <iostream>
 2 
 3 class TEST_A                 // 不是 上述 四种情况,有用户定义的 constructor,destructor
 4 {
 5     public:
 6         TEST_A(){}
 7         ~TEST_A(){}
 8         int a;
 9 };
10 
11 int main()
12 {
13     TEST_A test_a;
14     return 0;
15 }

反汇编:由于 TEST_A 还定义了 destructor, 故 main 函数 结束时,还 对 TEST_A 的对象 进行了 析构

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    53                       push   %ebx
 6    7:    83 ec 2c                 sub    $0x2c,%esp
 7    a:    8d 44 24 1c              lea    0x1c(%esp),%eax
 8    e:    89 04 24                 mov    %eax,(%esp)
 9   11:    e8 fc ff ff ff           call   12 <main+0x12>  // 调用 用户自定义 default constructor
10   16:    bb 00 00 00 00           mov    $0x0,%ebx
11   1b:    8d 44 24 1c              lea    0x1c(%esp),%eax
12   1f:    89 04 24                 mov    %eax,(%esp)
13   22:    e8 fc ff ff ff           call   23 <main+0x23>  // 调用 用户自定义 destructor
14   27:    89 d8                    mov    %ebx,%eax
15   29:    83 c4 2c                 add    $0x2c,%esp
16   2c:    5b                       pop    %ebx
17   2d:    89 ec                    mov    %ebp,%esp
18   2f:    5d                       pop    %ebp
19   30:    c3                       ret


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 1 #include <iostream>
 2 
 3 class TEST_A                // 用户 自定义 default constructor, 未 定义 destructor            
 4 {
 5     public:
 6         TEST_A(){}
 7         //~TEST_A(){}
 8         int a;
 9 };
10 class TEST_B:public TEST_A  // 普通 继承               
11 {
12     public:
13         int a;
14 };
15 
16 int main()
17 {
18     TEST_B test_b;
19     return 0;
20 }

反汇编:属于 case 2,TEST_B 继承自带有 default constructor 的 base class:TEST_A. 故 编译器 会 为其 合成 一个 default constructor,以保证 可以调用 

TEST_A 的 default constructor 生成 derived class:TEST_B 中的 TEST_A subobject. 此例 同样 没有 destructor 被 合成.

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    83 ec 20                 sub    $0x20,%esp
 6    9:    8d 44 24 18              lea    0x18(%esp),%eax
 7    d:    89 04 24                 mov    %eax,(%esp)
 8   10:    e8 fc ff ff ff           call   11 <main+0x11>   // 调用 合成 的 default constructor
 9   15:    b8 00 00 00 00           mov    $0x0,%eax
10   1a:    c9                       leave  
11   1b:    c3                       ret

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 1 #include <iostream>
 2 
 3 class TEST_A                
 4     public:
 5         TEST_A(){}
 6         ~TEST_A(){}
 7         int a;
 8 };
 9 class TEST_B:public TEST_A               
10 {
11     public:
12         int a;
13 };
14 
15 int main()
16 {
17     TEST_B test_b;
18     return 0;
19 }

反汇编:由于 base class 中 定义了 destructor,故 要 析构 derived class 中的 base class subobject,就需要调用base class 的 destructor,编译器通过 为 derived class 合成 一个 destructor,并在该 合成的 destructor 中 调用 base class 的 destructor 来达到目的.

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    53                       push   %ebx
 6    7:    83 ec 2c                 sub    $0x2c,%esp
 7    a:    8d 44 24 18              lea    0x18(%esp),%eax
 8    e:    89 04 24                 mov    %eax,(%esp)
 9   11:    e8 fc ff ff ff           call   12 <main+0x12>    // 调用 合成 的 default constructor
10   16:    bb 00 00 00 00           mov    $0x0,%ebx
11   1b:    8d 44 24 18              lea    0x18(%esp),%eax
12   1f:    89 04 24                 mov    %eax,(%esp)
13   22:    e8 fc ff ff ff           call   23 <main+0x23>    // 调用 合成 的 destructor
14   27:    89 d8                    mov    %ebx,%eax
15   29:    83 c4 2c                 add    $0x2c,%esp
16   2c:    5b                       pop    %ebx
17   2d:    89 ec                    mov    %ebp,%esp
18   2f:    5d                       pop    %ebp
19   30:    c3                       ret 

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

3. case 1, 含有 “带有 default constructor 的 member class object”

 1 #include <iostream>
 2 #include <string>
 3 
 4 class TEST_A               
 5 {
 6     public:
 7         std::string s;  // s 是带有 default constructor 的 member class object(string 类 具有 default constructor)
 8         int a;
 9 };
10 
11 int main()
12 {
13     TEST_A test_a;
14     return 0;
15 }

反汇编:由于 string 类 不仅 有 default constructor , 还有 destructor,s 作为 string 类 的对象,又是TEST_A 的成员,故 编译器 为 TEST_A 合成 了 default

constructor,也 合成了 destructor.

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    53                       push   %ebx
 6    7:    83 ec 2c                 sub    $0x2c,%esp
 7    a:    8d 44 24 18              lea    0x18(%esp),%eax
 8    e:    89 04 24                 mov    %eax,(%esp)
 9   11:    e8 fc ff ff ff           call   12 <main+0x12>   // 调用 编译器 合成的 default constructor
10   16:    bb 00 00 00 00           mov    $0x0,%ebx
11   1b:    8d 44 24 18              lea    0x18(%esp),%eax
12   1f:    89 04 24                 mov    %eax,(%esp)
13   22:    e8 fc ff ff ff           call   23 <main+0x23>   // 调用 编译器 合成的 destructor
14   27:    89 d8                    mov    %ebx,%eax
15   29:    83 c4 2c                 add    $0x2c,%esp
16   2c:    5b                       pop    %ebx
17   2d:    89 ec                    mov    %ebp,%esp
18   2f:    5d                       pop    %ebp
19   30:    c3                       ret

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

4. case 2,当一个class 派生自 ”带有 default constructor“的 base class

 1 #include <iostream>
 2 #include <string>
 3 
 4 class TEST_A           
 5 {
 6     public:
 7         std::string s;
 8         int a;
 9 };
10 class TEST_B:public TEST_A
11 {
12     public:
13         int a;
14 };
15 
16 int main()
17 {
18     TEST_B test_b;
19     return 0;
20 }

反汇编:因为 TEST_A 内含 member class object(此处为 string s),故 编译器 会为它 合成 default constructor 和 destructor;而 TEST_B 又 普通 继承 于

TEST_A , 故编译器 也会为 TEST_B 合成 default constructor 和 destructor

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    53                       push   %ebx
 6    7:    83 ec 2c                 sub    $0x2c,%esp
 7    a:    8d 44 24 14              lea    0x14(%esp),%eax
 8    e:    89 04 24                 mov    %eax,(%esp)
 9   11:    e8 fc ff ff ff           call   12 <main+0x12>   // 调用 编译器 合成的 default constructor
10   16:    bb 00 00 00 00           mov    $0x0,%ebx
11   1b:    8d 44 24 14              lea    0x14(%esp),%eax
12   1f:    89 04 24                 mov    %eax,(%esp)
13   22:    e8 fc ff ff ff           call   23 <main+0x23>   // 调用 编译器 合成的 destructor
14   27:    89 d8                    mov    %ebx,%eax
15   29:    83 c4 2c                 add    $0x2c,%esp
16   2c:    5b                       pop    %ebx
17   2d:    89 ec                    mov    %ebp,%esp
18   2f:    5d                       pop    %ebp
19   30:    c3                       ret

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

5. case 3 当一个class 声明(或 继承)一个 virtual function

 1 #include <iostream>
 2 
 3 class TEST_A           
 4 {
 5     public:
 6         virtual void func(){}    
 7         int a;
 8 };
 9 
10 int main()
11 {
12     TEST_A test_a;
13     return 0;
14 }

当一个 class 中声明了 virtual function 时
  1. 编译器会产生 一个 virtual function table,内放 class 的 virtual functions 地址;
  2. 在每一个 class object 中,编译器 会 合成 一个 pointer member(即 vptr),用来指向 class 的 vtbl (virtual function table).

        对于那些未声明任何 constructor 的 class, 编译器 会为 它们 合成 一个 default constructor, 以便正确的初始化 每一个 class object 的 vptr.

反汇编:TEST_A 未定义任何的 constructor,但又 含有 virtual function,为了使其 对象 能够 找到 类 的virtual functions 的地址,编译器会修改 类的定义,添加

一个 指针成员 (vptr),此时 编译器 还会 合成一个default constructor,用来 初始化 这个 vptr,使其指向 virtual function table.

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    83 ec 20                 sub    $0x20,%esp
 6    9:    8d 44 24 18              lea    0x18(%esp),%eax
 7    d:    89 04 24                 mov    %eax,(%esp)
 8   10:    e8 fc ff ff ff           call   11 <main+0x11>    // 调用 编译器 合成 的 default constructor
 9   15:    b8 00 00 00 00           mov    $0x0,%eax
10   1a:    c9                       leave  
11   1b:    c3                       ret

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

6. case 4 当一个class 派生自一个 继承串链,其中有一个 或 多个 virtual base class

 1 #include <iostream>
 2 
 3 class TEST_A           
 4 {
 5     public:
 6         int a;
 7 };
 8 class TEST_B:public virtual TEST_A
 9 {
10     public:
11         int a;
12 };
13 
14 int main()
15 {
16     TEST_B test_b;
17     return 0;
18 }

反汇编: 这个例子中 TEST_A, TEST_B,都没有 定义 default constructor,而且 TEST_A 也不会 被 编译器 合成 default construtor,但是 TEST_B 虚拟继承于TEST_A,从 汇编代码中 看到 在定义一个 TEST_B 对象时,调用了 构造函数,这个 构造函数 就是 编译器 为它 合成的 default constructor.

 1 00000000 <main>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 e4 f0                 and    $0xfffffff0,%esp
 5    6:    83 ec 20                 sub    $0x20,%esp
 6    9:    8d 44 24 14              lea    0x14(%esp),%eax
 7    d:    89 04 24                 mov    %eax,(%esp)
 8   10:    e8 fc ff ff ff           call   11 <main+0x11>    // 调用 编译器 合成的 default constructor
 9   15:    b8 00 00 00 00           mov    $0x0,%eax
10   1a:    c9                       leave  
11   1b:    c3                       ret

再 看例子 来说明 虚拟继承 时 为什么 要 合成 构造函数: (引用自 《深度探索C++对象模型》 P46)

  class X  { public: int i; };

  class A : public virtual X  { public: int j; };

  class B : public virtual X  { public: double d; };

  class C : public A, public B  { public: int k; };

  // 无法 在 编译期间 解析出 pa->X::i 的位置

  void foo ( const A* pa ) { pa->i = 1024;}

  main()

  {

    foo( new A );

    foo( new C );

    // ...

  }

  编译器无法固定foo() 中“经由 pa 而存取 X::i " 的 实际偏移位置,因为pa 的 真正类型 可以改变. 编译器 必须 改变 ”执行存取操作“ 的 那些代码,使X::i 可以延迟

到执行期 才决定下来. 所有 ”经由 reference 或 pointer 存取一个 virtual base classes “ 的操作都可以通过 相关指针 完成. 改写 foo()

  // 可能的 编译器 转变 操作

  void foo( const A * pa ) { pa -> __vbcX -> i = 1024; }

  其中 __vbcX 表示 编译器所产生的指针,指向 virtual base class X.

  __vbcX 是在 class object 构建 期间被完成的.  对于class 所定义的 每一个 constructor,编译器会 安插 那些 “允许 每一个 virtual base class 到 执行期存取操作”

的代码,如果 class 没有 声明 任何 constructors,编译器 必须 为它合成 一个 default constructor.

===================================================================================================================