C++对象模型:构造函数语意--Default Constructor 什么时候生成,又做了什么

引言

C++ Annotated Reference Manual中告诉我们:default constructors 在需要的时候被编译器产生出来;那么被谁需要?又做了什么事情呢?

一个简单的例子

#include <stdio.h>
class Foo { // 不会合成出一个default constructor
public:
    int val;
    Foo *pNext;
};

void FooBar()
{
    Foo bar; 
    if (bar.val || bar.pNext) {
        printf("bar's members is not all zero.\n"); // 实际运行分支
    } else {
        printf("bar's member is all zero).\n"); // 按照程序的预期,应该运行该分支
    }
    printf("bar.val:%d bar.pNext:%p \n", bar.val, bar.pNext);
}
int main()
{
	FooBar();
	return 0;
}

这个例子中,正确的程序语意是Foo有个default constructor,可以将它的两个member初始化为0;然而真正的输出是:

bar’s members is not all zero.
bar.val:4196352 bar.pNext:0x400650

两个member并没有被初始化为0;回头再看“default constructors 在需要的时候被编译器产生出来”这句话,这里要注意差别,该例子中是程序的需要,这句话是编译器的需要;程序的需要是程序员的责任,所以上述例子不会合成出一个default constructor;

确认到底有没有生成default constructor

可以使用二进制查看工具查看二进制中的是否存在构造函数的FUNC
1、没有生成默认构造函数的结果如下(即上述代码)

root@xxx:/home/yms/Test# readelf -s main | grep FUNC
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
30: 00000000004004a0 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones
31: 00000000004004e0 0 FUNC LOCAL DEFAULT 14 register_tm_clones
32: 0000000000400520 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux
35: 0000000000400540 0 FUNC LOCAL DEFAULT 14 frame_dummy
47: 0000000000400630 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
52: 0000000000400634 0 FUNC GLOBAL DEFAULT 15 _fini
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
54: 0000000000400566 73 FUNC GLOBAL DEFAULT 14 _Z6fooBarv
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
60: 00000000004005c0 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
62: 0000000000400470 42 FUNC GLOBAL DEFAULT 14 _start
64: 00000000004005af 16 FUNC GLOBAL DEFAULT 14 main
68: 0000000000400400 0 FUNC GLOBAL DEFAULT 11 _init

2、将上述Foo类修改成如下

class Demo {
public:
    Demo(){};
};

class Foo {
public:
    int val;
    Foo *pNext;
    Demo demo;
};

再看二进制中FUNC有哪些:

root@xxx:/home/yms/Test# readelf -s main | grep FUNC
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (3)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
30: 0000000000400510 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones
31: 0000000000400550 0 FUNC LOCAL DEFAULT 14 register_tm_clones
32: 0000000000400590 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux
35: 00000000004005b0 0 FUNC LOCAL DEFAULT 14 frame_dummy
47: 0000000000400700 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
51: 000000000040066a 31 FUNC WEAK DEFAULT 14 _ZN3FooC2Ev // // 此处为编译器产生的默认构造函数
53: 0000000000400704 0 FUNC GLOBAL DEFAULT 15 _fini
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@@GLIBC_2
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
56: 000000000040065e 11 FUNC WEAK DEFAULT 14 _ZN4DemoC2Ev
57: 00000000004005d6 120 FUNC GLOBAL DEFAULT 14 _Z6fooBarv
58: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
59: 000000000040066a 31 FUNC WEAK DEFAULT 14 _ZN3FooC1Ev // 此处为编译器产生的默认构造函数
64: 000000000040065e 11 FUNC WEAK DEFAULT 14 _ZN4DemoC1Ev
65: 0000000000400690 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
67: 00000000004004e0 42 FUNC GLOBAL DEFAULT 14 _start
69: 000000000040064e 16 FUNC GLOBAL DEFAULT 14 main
73: 0000000000400460 0 FUNC GLOBAL DEFAULT 11 _init

为什么有两个构造函数的符号,可以参考此回答

符号名字含义:

_Z | N | 3Foo | C1 | E | v
prefix | nested | Foo | Constructor | end nested | parameters: void

tips:另外还需注意,被编译器合成出来的constructor只执行编译器所需的行动,也就是说合成的default constructor不会将两个data members 初始化为0。

何时才会合成一个default constructor

答案是当编译器需要它的时候;那么什么情况下编译器会需要它呢,分为下面4种情况

  1. 带有Default Constructor的Member Class Object
    如果一个class没有任何constructor,但它内含一个member object,而后者有default constructor,那么这个class的implicit default constructor就是nontrival (也就是会合成一个default constructor),同上一节修改后的Foo类。
    如果default constructor已经被显示定义出来,那么编译无法合成第二个,那么编译器会是什么行为呢?
    行为如下:如果Class A内含一个或一个以上的member class objects,那么Class A的每一个constructor必须调用每一个member class的default constructor;编译器会扩张已存在的constructor,在其中安插一些代码,使得user code被执行之前,先调用必要的default constructor。
    如果有多个class member objects都要求constructor初始化操作,C++要求以member objects在class的声明顺序来调用各个constructor。
    示例如下:
#include <stdio.h>
#include <stdlib.h>

class Demo1 {
public:
   Demo1() {
       printf("Demo1 constructor\n");
   };
};
class Demo2 {
public:
   Demo2() {
       printf("Demo2 constructor\n");
   };
};
class Foo {
public:
   Foo() {
       printf("Foo constructor\n");
   };
   int val;
   Foo *pNext;
   Demo2 demo2;
   Demo1 demo1;
};

void fooBar()
{
   Foo bar;
}

int main() {
   fooBar();
   return 0;
}

输出:

root@xxx:/home/yms/Test# ./main
Demo2 constructor
Demo1 constructor
Foo constructor

  1. 带有Default Constructor的Base Class
    如果一个没有任何constructor的class派生自一个“带有default constructor”的base class,那么这个derived class的default constructor会被视为nontrivial。
    示例:
#include <stdio.h>
#include <stdlib.h>

class Base {
public:
    Base(){
        printf("Base constructor\n");
    };
};

class Foo : public Base {
public:
    int val;
    Foo *pNext;
};

void fooBar()
{
    Foo bar;
}

int main() {
    fooBar();
    return 0;
}

root@DAVINCI-D01-001:/home/yms/Test# readelf -s main1 | grep FUNC
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (3)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
30: 00000000004004d0 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones
31: 0000000000400510 0 FUNC LOCAL DEFAULT 14 register_tm_clones
32: 0000000000400550 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux
35: 0000000000400570 0 FUNC LOCAL DEFAULT 14 frame_dummy
47: 0000000000400690 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
51: 00000000004005fa 27 FUNC WEAK DEFAULT 14 _ZN3FooC2Ev
53: 0000000000400694 0 FUNC GLOBAL DEFAULT 15 _fini
54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@@GLIBC_2
55: 0000000000400596 58 FUNC GLOBAL DEFAULT 14 _Z6fooBarv
56: 00000000004005e0 25 FUNC WEAK DEFAULT 14 _ZN4BaseC1Ev
57: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
58: 00000000004005fa 27 FUNC WEAK DEFAULT 14 _ZN3FooC1Ev
62: 00000000004005e0 25 FUNC WEAK DEFAULT 14 _ZN4BaseC2Ev
64: 0000000000400620 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
66: 00000000004004a0 42 FUNC GLOBAL DEFAULT 14 _start
68: 00000000004005d0 16 FUNC GLOBAL DEFAULT 14 main
72: 0000000000400428 0 FUNC GLOBAL DEFAULT 11 _init

运行结果:

Base constructor

  1. 带有一个Virtual Function的Class
    另外两种情况也需要合成defaul constructor:class声明(或继承)一个virtual function、class派生自一个继承串链,其中有一个或更多的virtual base classes。
  2. 带有一个Virtual Base Class的Class

后面两种情况,大致都是为了满足多态,需在当前class中新增一个“指向虚表”的指针,这个指针需要在默认构造函数中初始化,virtual function机制或virtual base class机制,后序文章详细介绍。

总结

上述4种情况,会造成编译器必须为未声明constructor的class合成一个default constructor。C++ Standard把那些合成物称为implicit nontrival default constructor。被合成的constructor只能满足编译器(非程序)的需要。
在合成的default constructor中,只有base class subobjects和member class objects会被初始化,所有其他的nonstatic data member都不会被初始化。

常见的两个误解

  • 任何class如果没有定义default constructor,就会被合成出一个来。
  • 编译器合成出来的default constructor会显示设定“class 内每一个data member的默认值”。

全是假的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值