C++ 类- 构造和析构

news2025/1/23 3:19:40

空类

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.coloradmin.cn/o/2280669.html

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

相关文章

ubuntu20.04有亮度调节条但是调节时亮度不变

尝试了修改grub文件&#xff0c;没有作用&#xff0c;下载了brightness-controllor&#xff0c;问题解决了。 sudo add-apt-repository ppa:apandada1/brightness-controller sudo apt update sudo apt install brightness-controller 之后在应用软件中找到brightness-contro…

macOS如何进入 Application Support 目录(cd: string not in pwd: Application)

错误信息 cd: string not in pwd: Application 表示在当前目录下找不到名为 Application Support 的目录。可能的原因如下&#xff1a; 拼写错误或路径错误&#xff1a;确保你输入的目录名称正确。目录名称是区分大小写的&#xff0c;因此请确保使用正确的大小写。正确的目录名…

包文件分析器 Webpack Bundle Analyzer

webpack-bundle-analyzer 是一个非常有用的工具&#xff0c;用于可视化和分析 Webpack 打包生成的文件。这使得开发者能够更好地理解应用的依赖关系、包的大小&#xff0c;以及优化打包的机会。以下是关于 webpack-bundle-analyzer 的详细介绍&#xff0c;包括它的安装、使用以…

【深度解析Java 20天速成】04_IDEA的安装与使用

【Why IDEA ?】 【注】JetBrains官方说明&#xff1a; 尽管我们采取了多种措施确保受访者的代表性&#xff0c;但结果可能会略微偏向 JetBrains 产品的用户&#xff0c;因为这些用户更有可能参加调查。 此外&#xff0c;2022年&#xff0c;某美国软件开发商在对近千名专业的Ja…

算法竞赛之差分进阶——等差数列差分 python

目录 前置知识进入正题实战演练 前置知识 给定区间 [ l, r ]&#xff0c;让我们把数组中的[ l, r ] 区间中的每一个数加上c,即 a[ l ] c , a[ l 1 ] c , a[ l 2] c , a[ r ] c; 怎么做&#xff1f;很简单&#xff0c;差分一下即可 还不会的小伙伴点此进入学习 进入正题 …

【HarmonyOS NEXT】华为分享-碰一碰开发分享

关键词&#xff1a;鸿蒙、碰一碰、systemShare、harmonyShare、Share Kit 华为分享新推出碰一碰分享&#xff0c;支持用户通过手机碰一碰发起跨端分享&#xff0c;可实现传输图片、共享wifi等。我们只需调用系统 api 传入所需参数拉起对应分享卡片模板即可&#xff0c;无需对 U…

小程序 -- uni-app开发微信小程序环境搭建(HBuilder X+微信开发者工具)

目录 前言 一 软件部分 1. 微信开发者工具 2. HBuilder X 开发工具 二 配置部分 1. 关于 HBuilder X 配置 2. 关于 微信开发工具 配置 三 运行项目 1. 新建项目 2. 代码编写 3. 内置浏览器 编译 4. 配置小程序 AppID获取 注意 四 实现效果 前言 uni-app开发小程…

Element修改表格结构样式集合(后续实时更新)

场景 修改前端Element组件el-table样式 实现 线表格 <div class"tablepro"><el-table:data"tableData":header-cell-style"{ textAlign:center}"class"tablepro-table"borderstyle"width: 100%;height:100%"&g…

【C++】如何从源代码编译红色警戒2地图编辑器

【C】如何从源代码编译红色警戒2地图编辑器 操作视频视频中的代码不需要下载三方库&#xff0c;已经包含三方库。 一、运行效果&#xff1a;二、源代码来源及编程语言&#xff1a;三、环境搭建&#xff1a;安装红警2安装VS2022下载代码&#xff0c;源代码其实不太多&#xff0c…

[unity 高阶]使用ASE制作一个cubed的skybox的shader,跟做版本

第一步,导入ASE 此步骤不在此讲解,有时间再补充 第二步,创建shader 需要选择shader的类型,此处选择legacy/Unlit第三步,创建变量 根据默认shader中的变量 _Tint (“Tint Color”, Color) = (.5, .5, .5, .5)[Gamma] _Exposure (“Exposure”, Range(0, 8)) = 1.0_Rotat…

雷电9最新版安装Magisk+LSPosd(新手速通)

大家好啊&#xff01;我是NiJiMingCheng 我的博客&#xff1a;NiJiMingCheng 在安卓系统的定制与拓展过程中&#xff0c;获取 ROOT 权限以及安装各类框架是进阶玩家常用的操作&#xff0c;这可以帮助我们实现更多系统层面的个性化功能。今天&#xff0c;我将为大家详细介绍如何…

Spring Boot Starter介绍

前言 大概10来年以前&#xff0c;当时springboot刚刚出现并没有流行&#xff0c;当时的Java开发者们开发Web应用主要是使用spring整合springmvc或者struts、iBatis、hibernate等开发框架来进行开发。项目里一般有许多xml文件配置&#xff0c;其中配置了很多项目中需要用到的Be…

PyTorch使用教程(4)-如何使用torch.nn构建模型?

torch.nn 是 PyTorch 深度学习框架中的一个核心模块&#xff0c;专门用于构建和训练神经网络。它提供了一系列用于构建神经网络所需的组件&#xff0c;包括层&#xff08;Layers&#xff09;、激活函数&#xff08;Activation Functions&#xff09;、损失函数&#xff08;Loss…

Qt之QDjango-db的简单使用

QDjango是一款由C编写、依托于Qt库的Web开发框架&#xff0c;其设计理念受到了广受欢迎的Python框架Django的影响。这个项目旨在提供一个高效、灵活且易于使用的工具集&#xff0c;帮助开发者构建高质量的Web应用。其项目地址: https://gitcode.com/gh_mirrors/qd/qdjango&…

音频入门(二):音频数据增强

本文介绍了一些常见的音频数据增强方法&#xff0c;并给出了代码实现。 目录 一、简介 二、代码 1. 安装必要的库 2. 代码 3. 各函数的介绍 4. 使用方法 参考&#xff1a; 一、简介 音频数据增强是机器学习和深度学习领域中用于改善模型性能和泛化能力的技术。 使用数据…

【C++】引用(上)

1、引用的基本使用 作用&#xff1a;给变量起别名 语法&#xff1a;数据类型&#xff08;该数据类型要与原名的数据类型一致&#xff09; &别名原名&#xff1b; 示例&#xff1a; #include<iostream> using namespace std; int main() {int a 10;int& …

DBeaver下载安装及数据库连接(MySQL)

1. DBeaver下载 官网下载地址:Download | DBeaver Community 2. 安装 1. 双击下载的安装包&#xff0c;选择简体中文。 2. 点击下一步。 3. 点击我接受。 4. 如下勾选为所有用户安装&#xff0c;点击下一步。 5. 需重复做1~3 的步骤。 6. 选择组件&#xff0c;默认即可&…

leetcode 1620. 网络信号最好的坐标

题目如下 数据范围 示例 观察数据范围我们可以看到信号塔最多只有50座而x 与 y范围则是在0到50之间。 如果我们暴力枚举的话计算次数最多51 * 51 * 50时间复杂度即为O&#xff08;n * n * M&#xff09; 显然题目暗示我们使用枚举法通过代码 class Solution { public:vect…

《罗宾逊-旅途VR》Build2108907官方学习版

《罗宾逊-旅途VR》官方版 https://pan.xunlei.com/s/VODiY5gn_fNxKREdVRdwVboCA1?pwdsh3f# 从第一人称的角度进行探索&#xff0c;玩家将遇到一系列恐龙和生物&#xff0c;这些恐龙和生物会对它们在泰森三世生态系统中的存在做出反应。强调与周围环境的互动&#xff0c;鼓励玩…

Leetcode:2239

1&#xff0c;题目 2&#xff0c;思路 循环遍历满足条件就记录&#xff0c;最后返回结果值 3&#xff0c;代码 public class Leetcode2239 {public static void main(String[] args) {System.out.println(new Solution2239().findClosestNumber(new int[]{-4, -2, 1, 4, 8})…