C++ 类- 构造和析构

空类

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;
  1. 通过函数传参,传入形参是具体的一个类对象
class A {};
void function(A a)
  1. 在函数的内部返回一个类对象,可能会触发拷贝构造函数,但是编译器会进行返回值优化,如果存在优化就不会触发拷贝构造函数,如果把优化去掉,它优先触发的是移动拷贝构造函数,其次才会触发我们的拷贝构造函数
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++ 什么时候生成默认的拷贝构造函数

答:需要看是否符合位拷贝语义,如果不符合我们的位拷贝语义将会生成默认的拷贝构造函数,如果符合位拷贝语义的话,就不会生成默认的拷贝构造函数。编译器会为类执行位拷贝动作。就是执行默认的逐成员初始化的动作

  1. 某个类内部有类对象,内部包含拷贝构造函数(不管是被明确声明还是编译器生成)
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)

  1. 某个类继承自某个基类,基类包含拷贝构造函数(不管是被明确声明还是编译器生成)
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

在单继承当中,如果有成员类,会按照声明顺序进行构造,按照相反顺序进行析构。基类比成员类依赖性更强。
多继承 中基类按声明顺序构造,按相反顺序析构。基类比成员类依赖性更强。
总结:
构造函数的执行顺序是按照依赖性来进行构造的,依赖性越强越先构造,最后是自己类本身进行构造。析构是相反顺序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/67769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

集群、分布式及微服务间的区别与联系

目录 单体架构介绍集群和分布式架构集群和分布式集群和分布式区别和联系 微服务架构的引入微服务带来的挑战 总结 单体架构介绍 早期很多创业公司或者传统企业会把业务的所有功能实现都打包在一个项目中&#xff0c;这种方式就称为单体架构 以我们都很熟悉的电商系统为例&…

从Windows通过XRDP远程访问和控制银河麒麟ukey v10服务器,以及多次连接后黑屏的问题

从Windows通过XRDP远程访问和控制银河麒麟ukey v10服务器&#xff0c;以及多次连接后黑屏的问题。 安装 rdp 服务&#xff1a; yum install -y epel-release yum install -y xrdp或者如下&#xff1a; 可以通过下载rpm软件包&#xff0c;然后rpm方式安装。访问xrdp官网https…

Maven多环境打包方法配置

简单记录一下SpringBoot多环境打包配置方法&#xff0c;分部署环境和是否包含lib依赖包两个维度 目录 一、需求说明二、目录结构三、配置方案四、验证示例 一、需求说明 基于Spring Boot框架的项目分开发&#xff0c;测试&#xff0c;生产等编译部署环境&#xff08;每一个环境…

SpringMVC 实战指南:打造高效 Web 应用的秘籍

第一章&#xff1a;三层架构和MVC 三层架构&#xff1a; 开发服务器端&#xff0c;一般基于两种形式&#xff0c;一种 C/S 架构程序&#xff0c;一种 B/S 架构程序使用 Java 语言基本上都是开发 B/S 架构的程序&#xff0c;B/S 架构又分成了三层架构三层架构&#xff1a; 表现…

github汉化

本文主要讲述了github如何汉化的方法。 目录 问题描述汉化步骤1.打开github&#xff0c;搜索github-chinese2.打开项目&#xff0c;打开README.md3.下载安装脚本管理器3.1 在README.md中往下滑动&#xff0c;找到浏览器与脚本管理器3.2 选择浏览器对应的脚本管理器3.2.1 点击去…

阳振坤:AI 大模型的基础是数据,AI越发达,数据库价值越大

2024年1月12日&#xff0c;第四届OceanBase数据库大赛决赛在北京圆满落幕。在大赛的颁奖典礼上&#xff0c;OceanBase 首席科学家阳振坤老师为同学们献上了一场主题为“爱上数据库”的公开课&#xff0c;他不仅分享了个人的成长历程&#xff0c;还阐述了对数据库行业现状与未来…

2.1.3 第一个工程,点灯!

新建工程 点击菜单栏左上角,新建工程或者选择“文件”-“新建工程”,选择工程类型“标准工程”选择设备类型和编程语言,并指定工程文件名及保存路径,如下图所示: 选择工程类型为“标准工程” 选择主模块机型; 选择熟悉的编程语言; 填写工程名,选择存放路径; 确定。 编…

Chrome谷歌浏览器如何能恢复到之前的旧版本

升级了谷歌最新版不习惯&#xff0c;如何降级版本 未完待续。。 电脑中的Chrome谷歌浏览器升级到了最新版本&#xff0c;但是有种种的不适应&#xff0c;如何能恢复到之前的旧版本呢&#xff1f;我们来看看操作步骤&#xff0c;而且无需卸载重装。 怎么恢复Chrome 之前版本&a…

IO进程----进程

进程 什么是进程 进程和程序的区别 概念&#xff1a; 程序&#xff1a;编译好的可执行文件 存放在磁盘上的指令和数据的有序集合&#xff08;文件&#xff09; 程序是静态的&#xff0c;没有任何执行的概念 进程&#xff1a;一个独立的可调度的任务 执行一个程序分配资…

LabVIEW处理复杂系统和数据处理

LabVIEW 是一个图形化编程平台&#xff0c;广泛应用于自动化控制、数据采集、信号处理、仪器控制等复杂系统的开发。它的图形化界面使得开发人员能够直观地设计系统和算法&#xff0c;尤其适合处理需要实时数据分析、高精度控制和复杂硬件集成的应用场景。LabVIEW 提供丰富的库…

部署Metricbeat监测ES

官方参考文档 安装Metricbeat curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-7.17.27-linux-x86_64.tar.gztar xzvf metricbeat-7.17.27-linux-x86_64.tar.gz设置 Metricbeat连接到 Elasticsearch 进入metricbeat目录配置metricbeat.yml …

高效安全文件传输新选择!群晖NAS如何实现无公网IP下的SFTP远程连接

文章目录 前言1. 开启群晖SFTP连接2. 群晖安装Cpolar工具3. 创建SFTP公网地址4. 群晖SFTP远程连接5. 固定SFTP公网地址6. SFTP固定地址连接 前言 随着远程办公和数据共享成为新常态&#xff0c;如何高效且安全地管理和传输文件成为了许多人的痛点。如果你正在寻找一个解决方案…

为医院量身定制做“旧改”| 全视通物联网智慧病房

随着经济工作会议、卫生健康工作会议、“经济高质量发展成效”系列新闻发布会的依次召开&#xff0c;强基工程、三明医改、儿科和精神卫生服务年、中医药传承创新发展、促进生育、养老服务改革、病房改造提升行动...等关键词正成为新的热点&#xff0c;2025年卫生健康工作面临一…

PHP同城配送小程序

&#x1f680; 同城极速达——您生活中的极速配送大师 &#x1f4f1; 一款专为现代都市快节奏生活量身打造的同城配送小程序&#xff0c;同城极速达&#xff0c;集高效、便捷、智能于一身&#xff0c;依托ThinkPHPGatewayWorkerUniapp的强大架构&#xff0c;巧妙融合用户端、骑…

ipad和macbook同步zotero文献附件失败的解决办法

背景&#xff1a;我所有的文献及其附件pdf都是在台式机&#xff08;windows系统&#xff09;&#xff0c;想要把这些文献同步到云上&#xff0c;然后再从云上同步到平板和其他笔记本电脑比如macbook。文献同步虽已成功&#xff0c;但文献附件都无法打开。 平板报错如下&#xf…

个人学习 - 什么是Vim?

观我往旧&#xff0c;同我仰春 - 2025.1.10 声明 仅作为个人学习使用&#xff0c;仅供参考 本文所有解释参考笔者个人理解&#xff0c;最终目的是服务于自我学习&#xff0c; 如果你需要了解官方更规范的解释&#xff0c;请自行查阅 Vim 是什么 Vim 是一个强大的 文本编辑器…

RK3568上电启动流程详解 [十四]

由于 QEMU 在设备仿真方面的能力欠缺&#xff0c;比如我们无法让 QEMU 模拟一个 IIC 设备&#xff08;除非对 QEMU 的代码动刀子&#xff09;&#xff0c;所以我们需要一个真实的物理环境&#xff0c;这里我使用了烂大街的 RK3568 开发板&#xff0c;我们需要让 X-Hyper 在 RK3…

开关电源基础

文章目录 线性电源与开关电源选用 开关稳压器脉宽调制简化的降压开关电源 开关电源类型输出电压分拓扑分 控制器与稳压器效率与 V o u t V_{out} Vout​ 同步与非同步隔离与非隔离非隔离式拓扑结构隔离式拓扑结构 线性电源与开关电源 线性稳压器就是我们通常说的LDO: 传输元件…

微信小程序-base64加解密

思路&#xff1a;先创建一个base64.js的文件&#xff0c;这个文件可以作为专门加解密的文件模块&#xff0c;需要时就引用&#xff1b;创建好后&#xff0c;引用base64.js里的加解密函数。 注意&#xff1a;引用模块一定要引用正确的路径&#xff0c;否则会报错。 base64.js:…

C++《AVL树》

在之前的学习当中我们已经了解了二叉搜索树&#xff0c;并且我们知道二叉搜索树的查找效率是无法满足我们的要求&#xff0c;当二叉树为左或者右斜树查找的效率就很低下了&#xff0c;那么这本篇当中我们就要来学习对二叉搜索树进行优化的二叉树——AVL树。在此会先来了解AVL树…