空类
class A {};
空类大小:
sizeof(A) = 1
编译器会默认生成 6 个成员函数:
class A
{
public:
A();//构造函数 - 完成对象初始化工作
~A();//析构函数 - 完成对象的资源清理
A(const A& a);//拷贝构造函数 - 使用同一类中之前创建的对象来初始化新创建的对象
A& operator=(const A& a);//赋值运算符重载 - 让编译器按照指定的规则对自定义类型对象直接进行一些运算符操作
A* operator &();//取地址运算符重载
const A* operator &() const;//const修饰的取地址运算符重载
};
理论上来说会生成这部分,但是通过汇编来看空类并没有生成什么构造之类的函数:
class A {};
A a;
printf("%p",&a);
```cpp
00007FF6FC811103 | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] | test1.cpp:104
00007FF6FC811108 | 48:8D0D 41110000 | lea rcx,qword ptr ds:[7FF6FC812250] | 00007FF6FC812250:"%p"
00007FF6FC81110F | E8 4CFFFFFF | call <test1.printf> |
构造和析构函数:
默认构造:
必须要有一个且仅有一个默认构造函数
构造函数特点
- 构造函数与类同名
- 构造函数没有返回类型
- 构造函数由系统自动调用,不允许在程序张显式调用
- 构造函数可以被重载,既一个类中可以定义多个参数或参数类型不同的构造函数。
理解误区:
如果没有定义构造函数,就会生成一个默认构造函数 (×)
如果没有定义构造函数,会隐含声明一个默认构造函数,因为这个构造函数不会初始化成员变量 (√)
有用的默认构造函数在需要时被编译器生成(这里的需要是编译器需要,而不是程序员需要)
例如:
class Foo
{
public:
Foo() {};
Foo(int) {};
~Foo() {};
private:
};
class Bar
{
public:
Foo fo;
char std;
};
汇编代码:
------------------------------ 构造函数 ------------------------------
00007FF7904B1160 | 48:894C24 08 | mov qword ptr ss:[rsp+8],rcx | [rsp+08]:__scrt_release_startup_lock+D
00007FF7904B1165 | 48:83EC 28 | sub rsp,28 |
00007FF7904B1169 | 48:8B4424 30 | mov rax,qword ptr ss:[rsp+30] |
00007FF7904B116E | 48:8BC8 | mov rcx,rax |
00007FF7904B1171 | E8 6AFFFFFF | call <test1.public: __cdecl Foo::Foo(void)> |
00007FF7904B1176 | 48:8B4424 30 | mov rax,qword ptr ss:[rsp+30] |
00007FF7904B117B | 48:83C4 28 | add rsp,28 |
00007FF7904B117F | C3 | ret |
------------------------------ 析构函数 ------------------------------
00007FF7904B1180 | 48:894C24 08 | mov qword ptr ss:[rsp+8],rcx | [rsp+08]:__scrt_release_startup_lock+D
00007FF7904B1185 | 48:83EC 28 | sub rsp,28 |
00007FF7904B1189 | 48:8B4424 30 | mov rax,qword ptr ss:[rsp+30] |
00007FF7904B118E | 48:8BC8 | mov rcx,rax |
00007FF7904B1191 | E8 5AFFFFFF | call <test1.public: __cdecl Foo::~Foo(void)> |
00007FF7904B1196 | 48:83C4 28 | add rsp,28 |
00007FF7904B119A | C3 | ret |
------------------------------ main 函数 ------------------------------
00007FF7904B1123 | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] | test1.cpp:117
00007FF7904B1128 | E8 33000000 | call <test1.public: __cdecl Bar::Bar(void)> |
00007FF7904B112D | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] | test1.cpp:118
00007FF7904B1132 | 48:8D0D 17110000 | lea rcx,qword ptr ds:[7FF7904B2250] | 00007FF7904B2250:"%p"
00007FF7904B1139 | E8 22FFFFFF | call <test1.printf> |
00007FF7904B113E | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] | test1.cpp:124
00007FF7904B1143 | E8 38000000 | call <test1.public: __cdecl Bar::~Bar(void)> |
这里我们着重于能不能正确的初始化成员变量
Bar 类中有成员类对象 Foo ,成员类对象中提供了多个构造函数,所以
Bar 必须要生成一个默认构造函数,才能调用到成员类对象的构造函数。
class Base
{
public:
Base() { std::cout << "Base()" << std::endl; };
~Base() {};
};
class Derived :public Base {
public:
};
某个类继承自某个基类,基类包含构造函数(不管是被明确声明,还是编译器自动生成)为了正确调用基类的构造函数,所以C++编译器必须为 Derived 生成一个默认构造函数。
class A
{
public:
virtual void function() {};
};
A a;
printf("%p",&a);
在汇编中可以看到生成默认构造:
00007FF7350F1113 | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] | test1.cpp:106, rcx:const A::`vftable', [rsp+20]:const A::`vftable'
00007FF7350F1118 | E8 33000000 | call <test1.public: __cdecl A::A(void)> |
00007FF7350F111D | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] | test1.cpp:107, [rsp+20]:const A::`vftable'
00007FF7350F1122 | 48:8D0D 37110000 | lea rcx,qword ptr ds:[7FF7350F2260] | rcx:const A::`vftable', 00007FF7350F2260:"%p"
00007FF7350F1129 | E8 32FFFFFF | call <test1.printf> |
某个类包含虚函数。
虚函数是c++ 提供多态的特性,这里的多态是通过虚函数表指针,在对象构建的时候需要正确的设置虚函数表指针,所以编译器会为它生成一个默认的构造函数。从而能够正确的设置我们的虚函数表指针。
class X { public: int x; };
class A :public virtual X
{
public:
int a;
};
class B :public virtual X
{
public:
int b;
};
class C :public A,public B
{
public:
int c;
};
调用:
C a;
printf("%p",&a);
某个类存在虚继承,需要正确设置虚基表指针,如果不提供默认构造就不能正确设置虚基表指针,所以编译器会生成默认的构造函数
拷贝构造
通过一个类的对象去初始化另外一个对象。
什么时候触发拷贝构造函数
1.使用一个类对象,去初始化另外一个对象
class A {};
A a;
A b = a;
- 通过函数传参,传入形参是具体的一个类对象
class A {};
void function(A a)
- 在函数的内部返回一个类对象,可能会触发拷贝构造函数,但是编译器会进行返回值优化,如果存在优化就不会触发拷贝构造函数,如果把优化去掉,它优先触发的是移动拷贝构造函数,其次才会触发我们的拷贝构造函数
class A {};
A function(){
A a;
// ....
return a
}
理解误区
如果类没有提供拷贝构造,那么编译器会自动生成一个 (×)
在 深入探索 C++ 对象模型一书中的第二章 构造函数语意学中提到:
默认的拷贝构造函数执行的是位拷贝 (× √)
在概念上是等同的,编译器会为符合拷贝语义的 class 产生 “位拷贝” 实际执行的是默认逐成员初始化 (default memberwise initialize)。但是它并没有生成拷贝构造函数。
书中描述,一个良好的编译器可以为大部分类对象产生位拷贝,因为他们有位拷贝语义……
位拷贝语义是指我们在进行位拷贝的时候它不会出错。
但是他并不会生成拷贝构造函数,只有在必要的时候的时候才由编译器产生出来。
按照书中意思理解,就是如果我们使用位拷贝语义会出错的情况下,才会生成默认构造或者拷贝构造
在汇编中查看位拷贝
class A {};
A a;
A b = a;
可以看到汇编代码并没有生成默认的拷贝构造
00007FF78ACD1103 | 4C:8D4424 20 | lea r8,qword ptr ss:[rsp+20] | test1.cpp:104
00007FF78ACD1108 | 48:8D5424 21 | lea rdx,qword ptr ss:[rsp+21] |
00007FF78ACD110D | 48:8D0D 3C110000 | lea rcx,qword ptr ds:[7FF78ACD2250] | 00007FF78ACD2250:"a = %p b = %p"
00007FF78ACD1114 | E8 47FFFFFF | call <test1.printf> |
C++ 什么时候生成默认的拷贝构造函数
答:需要看是否符合位拷贝语义,如果不符合我们的位拷贝语义将会生成默认的拷贝构造函数,如果符合位拷贝语义的话,就不会生成默认的拷贝构造函数。编译器会为类执行位拷贝动作。就是执行默认的逐成员初始化的动作
- 某个类内部有类对象,内部包含拷贝构造函数(不管是被明确声明还是编译器生成)
class A {
public:
A() {};
A(const A& a) {};
A(const char* str) {};
~A() {};
};
class B{
public:
B(const A& a) {};
private:
int c;
A a;
};
调用:
A a;
B b1 = a;
B b2(b1);
printf("a = %p b = %p",&a,&b1);
汇编:
00007FF68B4D1163 | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] | test1.cpp:117
00007FF68B4D1168 | E8 73FFFFFF | call <test1.public: __cdecl A::A(void)> |
00007FF68B4D116D | 90 | nop |
00007FF68B4D116E | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] | test1.cpp:118
00007FF68B4D1173 | 48:8D4C24 28 | lea rcx,qword ptr ss:[rsp+28] |
00007FF68B4D1178 | E8 93FFFFFF | call <test1.public: __cdecl B::B(class A const &)> |
00007FF68B4D117D | 90 | nop |
00007FF68B4D117E | 48:8D5424 28 | lea rdx,qword ptr ss:[rsp+28] | test1.cpp:119
00007FF68B4D1183 | 48:8D4C24 30 | lea rcx,qword ptr ss:[rsp+30] |
00007FF68B4D1188 | E8 73000000 | call <test1.public: __cdecl B::B(class B const &)> |
00007FF68B4D118D | 4C:8D4424 28 | lea r8,qword ptr ss:[rsp+28] | test1.cpp:120
00007FF68B4D1192 | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] |
00007FF68B4D1197 | 48:8D0D C2200000 | lea rcx,qword ptr ds:[7FF68B4D3260] | 00007FF68B4D3260:"a = %p b = %p"
00007FF68B4D119E | E8 BDFEFFFF | call <test1.printf> |
00007FF68B4D11A3 | 48:8D4C24 30 | lea rcx,qword ptr ss:[rsp+30] | test1.cpp:126
00007FF68B4D11A8 | E8 33000000 | call <test1.public: __cdecl B::~B(void)> |
00007FF68B4D11AD | 90 | nop |
00007FF68B4D11AE | 48:8D4C24 28 | lea rcx,qword ptr ss:[rsp+28] |
00007FF68B4D11B3 | E8 28000000 | call <test1.public: __cdecl B::~B(void)> |
00007FF68B4D11B8 | 90 | nop |
00007FF68B4D11B9 | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] |
00007FF68B4D11BE | E8 3DFFFFFF | call <test1.public: __cdecl A::~A(void)> |
00007FF68B4D11C3 | 33C0 | xor eax,eax |
----------------------------------
可以看到 编译器生成了默认拷贝构造 test1.public: __cdecl B::B(class B const &)
在代码中 class A 我们明确声明 拷贝构造函数 A(const A& a) {};
在代码中 class B 里面有一个内部类对象 A a; 我们在构造B时肯定先构造 内部成员 A a;
所以这个 class B必须生成一个默认拷贝构造函数去调用 class A 中的默认拷贝构造函数 A(const A& a)
- 某个类继承自某个基类,基类包含拷贝构造函数(不管是被明确声明还是编译器生成)
class A {
public:
A() {};
A(const A& a) {};
};
class B: public A {
public:
};
调用:
B b1;
B b2(b1);
printf("a = %p b = %p",&a,&b1);
汇编:
00007FF6D3651123 | 48:8D4C24 21 | lea rcx,qword ptr ss:[rsp+21] | test1.cpp:111
00007FF6D3651128 | E8 B3FFFFFF | call <test1.public: __cdecl A::A(void)> |
00007FF6D365112D | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] | test1.cpp:112
00007FF6D3651132 | E8 39000000 | call <test1.public: __cdecl B::B(void)> |
00007FF6D3651137 | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] | test1.cpp:113
00007FF6D365113C | 48:8D4C24 22 | lea rcx,qword ptr ss:[rsp+22] |
00007FF6D3651141 | E8 4A000000 | call <test1.public: __cdecl B::B(class B const &)> |
00007FF6D3651146 | 4C:8D4424 20 | lea r8,qword ptr ss:[rsp+20] | test1.cpp:114
00007FF6D365114B | 48:8D5424 21 | lea rdx,qword ptr ss:[rsp+21] |
00007FF6D3651150 | 48:8D0D F9100000 | lea rcx,qword ptr ds:[7FF6D3652250] | 00007FF6D3652250:"a = %p b = %p"
00007FF6D3651157 | E8 04FFFFFF | call <test1.printf> |
由于我们构建派生类的时候需要先构基类,基类有一个默认拷贝构造函数,所以在子类中编译器帮我们生成了一个默认拷贝构造函数用来调用基类的默认拷贝构造函数。
3.某个类包含虚函数:
class ZoomAnimal {
public:
virtual void draw() {};
};
class Bear :public ZoomAnimal
{
public:
void draw() {};
virtual void dance() {};
};
调用:
Bear yogi;
Bear winnie = yogi;
00007FF6E3AC1113 | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] | test1.cpp:116
00007FF6E3AC1118 | E8 43000000 | call <test1.public: __cdecl Bear::Bear(void)> |
00007FF6E3AC111D | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] | test1.cpp:117
00007FF6E3AC1122 | 48:8D4C24 28 | lea rcx,qword ptr ss:[rsp+28] |
00007FF6E3AC1127 | E8 84000000 | call <test1.public: __cdecl Bear::Bear(class Bear const &)> |
00007FF6E3AC112C | 4C:8D4424 28 | lea r8,qword ptr ss:[rsp+28] | test1.cpp:118
00007FF6E3AC1131 | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] |
00007FF6E3AC1136 | 48:8D0D 23110000 | lea rcx,qword ptr ds:[7FF6E3AC2260] | 00007FF6E3AC2260:"a = %p b = %p"
00007FF6E3AC113D | E8 1EFFFFFF | call <test1.printf> |
在编译器编译虚函数的时候默认会生成一个虚表指针,由于 winnie 和 yogi 是同一个类它指向的是相同的虚
函数表,但是如果 ZoomAnimal 用 Bear 去初始化 ,由于他们的虚函数表是不同的不能用 Bear 虚表去赋值
ZoomAnimal 的虚表指针,所以会为 ZoomAnimal 生成一个默认拷贝构造去正确赋值虚表指针:
00007FF75E6D1127 | E8 84000000 | call <test1.public: __cdecl ZoomAnimal::ZoomAnimal(class ZoomAnimal const &)> |
调用:
Bear yogi;
ZoomAnimal franny = yogi;
printf("a = %p b = %p",&yogi,&franny);
00007FF75E6D1113 | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] | test1.cpp:112
00007FF75E6D1118 | E8 43000000 | call <test1.public: __cdecl Bear::Bear(void)> |
00007FF75E6D111D | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] | test1.cpp:115
00007FF75E6D1122 | 48:8D4C24 28 | lea rcx,qword ptr ss:[rsp+28] |
00007FF75E6D1127 | E8 84000000 | call <test1.public: __cdecl ZoomAnimal::ZoomAnimal(class Zoom |
00007FF75E6D112C | 4C:8D4424 28 | lea r8,qword ptr ss:[rsp+28] | test1.cpp:116
00007FF75E6D1131 | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] |
00007FF75E6D1136 | 48:8D0D 23110000 | lea rcx,qword ptr ds:[7FF75E6D2260] | 00007FF75E6D2260:"a = %p b = %p"
00007FF75E6D113D | E8 1EFFFFFF | call <test1.printf> |
4.某个类存在虚继承
class A {};
class B: public virtual A {};
class C: public virtual B {};
调用:
B b1;
B b2 = b1;
printf("b1 = %p b2 = %p",&b1,&b2);
汇编:
00007FF61D4D1103 | BA 01000000 | mov edx,1 | test1.cpp:106
00007FF61D4D1108 | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] |
00007FF61D4D110D | E8 4E000000 | call <test1.public: __cdecl B::B(void)> |
00007FF61D4D1112 | 41:B8 01000000 | mov r8d,1 | test1.cpp:107
00007FF61D4D1118 | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] |
00007FF61D4D111D | 48:8D4C24 28 | lea rcx,qword ptr ss:[rsp+28] |
00007FF61D4D1122 | E8 69000000 | call <test1.public: __cdecl B::B(class B const &)> |
00007FF61D4D1127 | 4C:8D4424 28 | lea r8,qword ptr ss:[rsp+28] | test1.cpp:108
00007FF61D4D112C | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] |
00007FF61D4D1131 | 48:8D0D 18110000 | lea rcx,qword ptr ds:[7FF61D4D2250] | 00007FF61D4D2250:"b1 = %p b2 = %p"
00007FF61D4D1138 | E8 23FFFFFF | call <test1.printf> |
调用:
C c1;
B b1 = b1;
printf("b1 = %p c1 = %p",&b1,&c1);
汇编:
00007FF7665D1103 | BA 01000000 | mov edx,1 | test1.cpp:104
00007FF7665D1108 | 48:8D4C24 28 | lea rcx,qword ptr ss:[rsp+28] |
00007FF7665D110D | E8 4E000000 | call <test1.public: __cdecl C::C(void)> |
00007FF7665D1112 | 41:B8 01000000 | mov r8d,1 | test1.cpp:105
00007FF7665D1118 | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] |
00007FF7665D111D | 48:8D4C24 20 | lea rcx,qword ptr ss:[rsp+20] |
00007FF7665D1122 | E8 89000000 | call <test1.public: __cdecl B::B(class B const &)> |
00007FF7665D1127 | 4C:8D4424 28 | lea r8,qword ptr ss:[rsp+28] | test1.cpp:106
00007FF7665D112C | 48:8D5424 20 | lea rdx,qword ptr ss:[rsp+20] |
00007FF7665D1131 | 48:8D0D 18110000 | lea rcx,qword ptr ds:[7FF7665D2250] | 00007FF7665D2250:"b1 = %p c1 = %p"
00007FF7665D1138 | E8 23FFFFFF | call <test1.printf> |
析构函数:
- 析构函数
- 主要用于对象生命结束时回收对象
- 与类同名,在其前面加上字符"~"
- 没有返回值
- 一个类只有一个析构函数,没有无参数
虚析构函数作用
作用:在继承下,为了使子类析构函数能得到正常调用,需要将基类的析构函数设置为虚析构函数。
早绑定和晚绑定
早绑定是指在编译时确定函数调用的地址,而晚绑定是指在运行时确定函数调用的地址
什么场景可能会出现析构函数不能得到正常的调用?
- 子类对象指针赋值给基类指针,在调用析构函数的时候,子类对象的析构函数得不到调用
class A
{
public:
A() { cout << "[+]: 构造函数 A \n"; }
~A() { cout << "[-]: 析构函数 A \n";}
};
class B: public A
{
public:
B() { cout << "[+]: 构造函数 B \n"; }
~B() { cout << "[-]: 析构函数 B \n"; }
};
调用:
A* p = new B(); // 早绑定
delete p;
输出:
[+]: 构造函数 A
[+]: 构造函数 C
[-]: 析构函数 A
汇编:
00007FF76A1710B4 | B9 01000000 | mov ecx,1 | test1.cpp:41
00007FF76A1710B9 | E8 82070000 | call <test1.void * __cdecl operator new(unsigned __int64)> |
00007FF76A1710BE | 48:894424 20 | mov qword ptr ss:[rsp+20],rax |
00007FF76A1710C3 | 48:837C24 20 00 | cmp qword ptr ss:[rsp+20],0 |
00007FF76A1710C9 | 74 11 | je test1.7FF76A1710DC |
00007FF76A1710CB | 48:8B4C24 20 | mov rcx,qword ptr ss:[rsp+20] |
00007FF76A1710D0 | E8 8BFFFFFF | call <test1.public: __cdecl C::C(void)> |
00007FF76A1710D5 | 48:894424 28 | mov qword ptr ss:[rsp+28],rax | [rsp+28]:__scrt_release_startup_lock+D
00007FF76A1710DA | EB 09 | jmp test1.7FF76A1710E5 |
00007FF76A1710DC | 48:C74424 28 00000000 | mov qword ptr ss:[rsp+28],0 | [rsp+28]:__scrt_release_startup_lock+D
00007FF76A1710E5 | 48:8B4424 28 | mov rax,qword ptr ss:[rsp+28] | [rsp+28]:__scrt_release_startup_lock+D
00007FF76A1710EA | 48:894424 38 | mov qword ptr ss:[rsp+38],rax |
00007FF76A1710EF | 48:8B4424 38 | mov rax,qword ptr ss:[rsp+38] |
00007FF76A1710F4 | 48:894424 40 | mov qword ptr ss:[rsp+40],rax |
00007FF76A1710F9 | 48:8B4424 40 | mov rax,qword ptr ss:[rsp+40] | test1.cpp:42
00007FF76A1710FE | 48:894424 30 | mov qword ptr ss:[rsp+30],rax |
00007FF76A171103 | 48:837C24 30 00 | cmp qword ptr ss:[rsp+30],0 |
这里我们会将 B 强转到 A,本质上是将 p 指向了 B 类中的 A 这一部分,我们去析构它的
时候也只析构了A 这一部分。
添加一个虚函数, virtual void function() {};以修改为晚绑定:
class A
{
public:
A() { cout << "[+]: 构造函数 A \n"; }
~A() { cout << "[-]: 析构函数 A \n";}
virtual void function() {};
};
class B: public A
{
public:
B() { cout << "[+]: 构造函数 B \n"; }
~B() { cout << "[-]: 析构函数 B \n"; }
};
调用:
A* p = new B();
delete p;
输出:
[+]: 构造函数 A
[+]: 构造函数 B
[-]: 析构函数 A
我们的虚表指针是在构造函数中构造的(见下面汇编):
.text:00007FF67AFD1090 <public: __cdecl B::B(void)>
00007FF67AFD1090 <tes | 48:894C24 08 | mov qword ptr ss:[rsp+8],rcx | test1.cpp:30
00007FF67AFD1095 | 48:83EC 28 | sub rsp,28 |
00007FF67AFD1099 | 48:8B4C24 30 | mov rcx,qword ptr ss:[rsp+30] | [rsp+30]:__scrt_release_startup_lock+D
00007FF67AFD109E | E8 5DFFFFFF | call <test1.public: __cdecl A::A(void)> |
00007FF67AFD10A3 | 90 | nop |
00007FF67AFD10A4 | 48:8B4424 30 | mov rax,qword ptr ss:[rsp+30] | [rsp+30]:__scrt_release_startup_lock+D
00007FF67AFD10A9 | 48:8D0D 48230000 | lea rcx,qword ptr ds:[<const B::`vftable'>] |
00007FF67AFD10B0 | 48:8908 | mov qword ptr ds:[rax],rcx |
00007FF67AFD10B3 | 48:8D15 06230000 | lea rdx,qword ptr ds:[<"[+]: \xB9\xB9\xD4\xEC\xBA\xAF\xCA\xFD B \n"...>] | 00007FF67AFD33C0:"[+]: 构造函数 B \n"
00007FF67AFD10BA | 48:8B0D 27200000 | mov rcx,qword ptr ds:[<class std::basic_ostream<char, struct std::char_traits<cha |
00007FF67AFD10C1 | E8 8A010000 | call <test1.class std::basic_ostream<char, struct std::char_traits<char>> & __cde |
00007FF67AFD10C6 | 90 | nop |
00007FF67AFD10C7 | 48:8B4424 30 | mov rax,qword ptr ss:[rsp+30] | [rsp+30]:__scrt_release_startup_lock+D
00007FF67AFD10CC | 48:83C4 28 | add rsp,28 |
00007FF67AFD10D0 | C3 | ret |
但是我们在代码中没有显示的写这个构造函数,编译器会为我们的类A 和 类B 生成默认的构造函数,那么按照
依赖性顺序,B继承A,会先构造 A,构造A的时候A的虚表指针指向的地址赋值给B对象的vptr
然后执行 B 构造函数的时候,由于我们没有复写 function 所以就没有修改这个 vptr,详见汇编:
00007FF67AFD10A9 | 48:8D0D 48230000 | lea rcx,qword ptr ds:[<const B::`vftable'>] |
00007FF67AFD10B0 | 48:8908 | mov qword ptr ds:[rax],rcx | [rax]:const B::`vftable'
[rax] 本身是A的虚表(A构造时赋值),在00007FF67AFD10B0这条汇编被改写成的 B的虚表的地址
这里会有两次覆盖,第一次是在A构造时对 vptr 进行赋值,第二次是在B构造时复写这个 vptr 修改为B的虚表
现在虽然是晚绑定,但是我们调用还是A类,但是 ~A() 不是虚函数,所以还是只调用A的析构函数。
使用虚析构,让类正常析构:
class A
{
public:
A() { cout << "[+]: 构造函数 A \n"; }
virtual ~A() { cout << "[-]: 析构函数 A \n";}
};
class B: public A
{
public:
B() { cout << "[+]: 构造函数 B \n"; }
~B() { cout << "[-]: 析构函数 B \n"; }
};
调用:
A* p = new B();
delete p;
输出:
[+]: 构造函数 A
[+]: 构造函数 B
[-]: 析构函数 B
[-]: 析构函数 A
在 C++ 看来,我们设计某个类的时候不一定是基类,如果该类是基类的话,我们应该手动的将
基类的析构函数设置为虚函数,这样就触发了多态。这样就是得我们的p指向了B而不是指向A
main:
00007FF6F62D11A9 | E8 42070000 | call <test1.void * __cdecl operator new(unsigned __int64)> |
00007FF6F62D11AE | 48:894424 28 | mov qword ptr ss:[rsp+28],rax |
00007FF6F62D11B3 | 48:837C24 28 00 | cmp qword ptr ss:[rsp+28],0 |
00007FF6F62D11B9 | 74 11 | je test1.7FF6F62D11CC |
00007FF6F62D11BB | 48:8B4C24 28 | mov rcx,qword ptr ss:[rsp+28] |
00007FF6F62D11C0 | E8 FBFEFFFF | call <test1.public: __cdecl B::B(void)> |
00007FF6F62D11C5 | 48:894424 30 | mov qword ptr ss:[rsp+30],rax |
00007FF6F62D11CA | EB 09 | jmp test1.7FF6F62D11D5 |
00007FF6F62D11CC | 48:C74424 30 00000000 | mov qword ptr ss:[rsp+30],0 |
--------------------------------
00007FF6F62D1110 <test1. | 48:894C24 08 | mov qword ptr ss:[rsp+8],rcx | test1.cpp:30, [rsp+08]:class std::basic_ostream<char, struct std::char_traits<char>> std::cout
00007FF6F62D1115 | 48:83EC 28 | sub rsp,28 |
00007FF6F62D1119 | 48:8B4424 30 | mov rax,qword ptr ss:[rsp+30] |
00007FF6F62D111E | 48:8D0D EB220000 | lea rcx,qword ptr ds:[<const B::`vftable'>] |
00007FF6F62D1125 | 48:8908 | mov qword ptr ds:[rax],rcx | [rax]:const B::`vftable'
00007FF6F62D1128 | 48:8D15 A9220000 | lea rdx,qword ptr ds:[<"[-]: \xCE\xF6\xB9\xB9\xBA\xAF\xCA\xFD B \n"...>] | 00007FF6F62D33D8:"[-]: 析构函数 B \n"
00007FF6F62D112F | 48:8B0D B21F0000 | mov rcx,qword ptr ds:[<class std::basic_ostream<char, struct std::char_traits<cha |
00007FF6F62D1136 | E8 85010000 | call <test1.class std::basic_ostream<char, struct std::char_traits<char>> & __cde |
00007FF6F62D113B | 48:8B4C24 30 | mov rcx,qword ptr ss:[rsp+30] |
00007FF6F62D1140 | E8 FBFEFFFF | call <test1.public: virtual __cdecl A::~A(void)> |
00007FF6F62D1145 | 90 | nop |
00007FF6F62D1146 | 48:83C4 28 | add rsp,28 |
00007FF6F62D114A | C3 | ret |
构造析构函数调用顺序
class A
{
public:
A() { cout << "[+]: 构造函数 A ,global(全局变量) \n";}
~A() { cout << "[-]: 析构函数 A ,global(全局变量) \n";}
};
class B
{
public:
B(){ cout << "[+]: 构造函数 B ,static_object(静态变量) \n"; }
~B() { cout << "[-]: 析构函数 B ,static_object(静态变量) \n"; }
};
class C
{
public:
C() { cout << "[+]: 构造函数 C ,local_object(局部变量) \n"; }
~C() { cout << "[-]: 析构函数 C,local_object(局部变量) \n"; }
};
A global; // 全局变量
int main()
{
MessageBoxA(0, 0, 0, 0);
cout << "Beginning of function.\n"; // 函数开始
static B static_object; // 静态变量
{ // 局部变量所在作用域开始
C local_object; // 局部变量
} // 局部变量所在作用域结束
cout << "End of function.\n"; // 函数结束
return 0;
}
输出打印:
[+]: 构造函数 A ,global(全局变量)
Beginning of function.
[+]: 构造函数 B ,static_object(静态变量)
[+]: 构造函数 C ,local_object(局部变量)
[-]: 析构函数 C,local_object(局部变量)
End of function.
[-]: 析构函数 B ,static_object(静态变量)
[-]: 析构函数 A ,global(全局变量)
继承下的构造函数和析构函数执行顺序
继承下,构造函数按照依赖链,从上往下构造。
继承下,析构函数按照依赖链,从下往上析构。
class A
{
public:
A() { cout << "[+]: 构造函数 A \n";}
~A() { cout << "[-]: 析构函数 A \n";}
};
class B:public A
{
public:
B(){ cout << "[+]: 构造函数 B \n"; }
~B() { cout << "[-]: 析构函数 B \n"; }
};
class C :public B
{
public:
C() { cout << "[+]: 构造函数 C \n"; }
~C() { cout << "[-]: 析构函数 C \n"; }
};
调用:
{
C c;
}
输出:
[+]: 构造函数 A
[+]: 构造函数 B
[+]: 构造函数 C
[-]: 析构函数 C
[-]: 析构函数 B
[-]: 析构函数 A
添加成员 看顺序:
class X
{
public:
X() { cout << "[+]: 构造函数 X \n"; }
~X() { cout << "[-]: 析构函数 X \n"; }
};
class A
{
public:
A() { cout << "[+]: 构造函数 A \n";}
~A() { cout << "[-]: 析构函数 A \n";}
private:
X x;
};
class B:public A
{
public:
B(){ cout << "[+]: 构造函数 B \n"; }
~B() { cout << "[-]: 析构函数 B \n"; }
};
class C :public B
{
public:
C() { cout << "[+]: 构造函数 C \n"; }
~C() { cout << "[-]: 析构函数 C \n"; }
};
调用:
C c;
输出:
[+]: 构造函数 X
[+]: 构造函数 A
[+]: 构造函数 B
[+]: 构造函数 C
[-]: 析构函数 C
[-]: 析构函数 B
[-]: 析构函数 A
[-]: 析构函数 X
再次添加成员:
class X
{
public:
X() { cout << "[+]: 构造函数 X \n"; }
~X() { cout << "[-]: 析构函数 X \n"; }
};
class Y
{
public:
Y() { cout << "[+]: 构造函数 Y \n"; }
~Y() { cout << "[-]: 析构函数 Y \n"; }
};
class A
{
public:
A() { cout << "[+]: 构造函数 A \n";}
~A() { cout << "[-]: 析构函数 A \n";}
private:
X x;
};
class B:public A
{
public:
B(){ cout << "[+]: 构造函数 B \n"; }
~B() { cout << "[-]: 析构函数 B \n"; }
private:
Y y;
};
class C :public B
{
public:
C() { cout << "[+]: 构造函数 C \n"; }
~C() { cout << "[-]: 析构函数 C \n"; }
};
调用:
[+]: 构造函数 X
[+]: 构造函数 A
[+]: 构造函数 Y
[+]: 构造函数 B
[+]: 构造函数 C
[-]: 析构函数 C
[-]: 析构函数 B
[-]: 析构函数 Y
[-]: 析构函数 A
[-]: 析构函数 X
class A1
{
public:
A1() { cout << "[+]: 构造函数 A1 \n";}
~A1() { cout << "[-]: 析构函数 A1 \n";}
private:
};
class A2
{
public:
A2() { cout << "[+]: 构造函数 A2 \n"; }
~A2() { cout << "[-]: 析构函数 A2 \n"; }
private:
};
class B: public A1, public A2
{
public:
B() { cout << "[+]: 构造函数 B \n"; }
~B() { cout << "[-]: 析构函数 B \n"; }
private:
};
调用:
B b;
输出:
[+]: 构造函数 A1
[+]: 构造函数 A2
[+]: 构造函数 B
[-]: 析构函数 B
[-]: 析构函数 A2
[-]: 析构函数 A1
class A1
{
public:
A1() { cout << "[+]: 构造函数 A1 \n";}
~A1() { cout << "[-]: 析构函数 A1 \n";}
private:
};
class A2
{
public:
A2() { cout << "[+]: 构造函数 A2 \n"; }
~A2() { cout << "[-]: 析构函数 A2 \n"; }
private:
};
class B1
{
public:
B1() { cout << "[+]: 构造函数 B1 \n"; }
~B1() { cout << "[-]: 析构函数 B1 \n"; }
private:
};
class B2
{
public:
B2() { cout << "[+]: 构造函数 B2 \n"; }
~B2() { cout << "[-]: 析构函数 B2 \n"; }
private:
};
class C: public A1,public A2
{
public:
C() { cout << "[+]: 构造函数 C \n"; }
~C() { cout << "[-]: 析构函数 C \n"; }
private:
B1 b1;
B2 b2;
};
调用:
C c;
输出:
[+]: 构造函数 A1
[+]: 构造函数 A2
[+]: 构造函数 B1
[+]: 构造函数 B2
[+]: 构造函数 C
[-]: 析构函数 C
[-]: 析构函数 B2
[-]: 析构函数 B1
[-]: 析构函数 A2
[-]: 析构函数 A1
在单继承当中,如果有成员类,会按照声明顺序进行构造,按照相反顺序进行析构。基类比成员类依赖性更强。
多继承 中基类按声明顺序构造,按相反顺序析构。基类比成员类依赖性更强。
总结:
构造函数的执行顺序是按照依赖性来进行构造的,依赖性越强越先构造,最后是自己类本身进行构造。析构是相反顺序。