虚析构函数的作用是什么?

virtual 析构函数的作用

虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的.

调用时机

在谈论实现之前先明确一下析构函数的调用时机

  1. 局部变量在作用域结束的时候,程序会默认调用析构函数(如果有的话)
  2. 主动调用delete(delete [])方法, 首先会查看对象类型,确定是否需要调用虚构函数. 如果发现对象的析构函数是virtual 的,就会使用virtual调用机制.

对象布局

要想了解virtual调用机制,首先就要清楚对象布局

@图1
如果派生类增加成员,则简单的放在基类的成员后面

image
vptr 虚指针: 指向对象的虚函数表
vtbl 虚函数表 : 存储对象虚函数地址

派生类通过vptr 找到vtbl,然后找到对应的虚函数进行调用

覆盖(overriding)

定义一个和基类中虚函数名字和类型都相同的函数,以使派生类的函数代替基类中的版本被放入vtal的技术成为 覆盖(overriding)

virtual 函数调用机制

本质 : 虚函数调用产生的目标代码首先简单的寻址vptr ,通过它找到对应的vtbl,然后调用其中正确的函数. 其代价大约调用了两次内存访问加上一次普通的函数调用.

Demo 实践检验真理

#include <iostream>

using namespace std;
class B {
public:
    virtual void f() const { cout << "B::f1()\n"; }
    virtual void f2() const { cout << "B::f2()\n"; }
    virtual void f3() const { cout << "B::f3()\n"; }
    virtual void f4() const { cout << "B::f4()\n"; }
    void g(){ cout << "B::g()\n"; }
};
struct D : public B{
public:
    void f() const override{ cout << "D::f1()\n"; }
    void f4() const override{ cout << "D::f4()\n"; }

    void g(){ cout << "D::g()\n"; }
};
struct DD : public  D {
public:
    void f() { cout << "DD::f1()\n"; }
    void g() { cout << "DD::g()\n"; }
};

typedef void (*func)();
void call(B& b ) {
    for(int i =0 ; i < 4 ; i++) { 
        unsigned long* vtbl = (unsigned long*)(*(unsigned long*)&base) + i; 
        cout << "slot address: " << vtbl << endl;
        cout << "func address: " << *vtbl << endl;
        func pfunc = (func)*(vtbl);
        pfunc();
    }
    b.g();
    cout << "++++++++++++++++++++++++++++++++++++++++++" << endl;
}

int main(int argc, const char * argv[]) {
        
    B b;
    D d;
    DD dd;
     
    call(b);
    call(d);
    call(dd);
     
    b.f();
    b.g(); 
	
    d.f();
    d.g();
    
    dd.f();
    dd.g(); 
    return 0;
}

结果:

slot address: 0x1000040e0
func address: 4294979760
B::f1()
slot address: 0x1000040e8
func address: 4294980032
B::f2()
slot address: 0x1000040f0
func address: 4294980080
B::f3()
slot address: 0x1000040f8
func address: 4294980128
B::f4()
B::g()
++++++++++++++++++++++++++++++++++++++++++
slot address: 0x100004120
func address: 4294979808
D::f1()
slot address: 0x100004128
func address: 4294980032
B::f2()
slot address: 0x100004130
func address: 4294980080
B::f3()
slot address: 0x100004138
func address: 4294980240
D::f4()
B::g()
++++++++++++++++++++++++++++++++++++++++++
slot address: 0x100004168
func address: 4294979808
D::f1()
slot address: 0x100004170
func address: 4294980032
B::f2()
slot address: 0x100004178
func address: 4294980080
B::f3()
slot address: 0x100004180
func address: 4294980240
D::f4()
B::g()
++++++++++++++++++++++++++++++++++++++++++
B::f1()
B::g()
D::f1()
D::g()
DD::f1()
DD::g()
Program ended with exit code: 0

代码分析

对象b 通过虚函数表分别调用了自己的f f1 f2 f3

对应的函数地址:
f : 4294979760
f2: 4294980032
f3: 4294980080
f4: 4294980128

对象d 通过虚函数表分别调用了 父类的f2 f3 调用了覆盖函数 f f4

对应函数地址:
f: 4294979808
f2: 4294980032
f3: 4294980080
f4: 4294980240

对象dd 通过虚函数表分别调用了 父类的父级的f2 f3 调用了父级的函数 f f4

对应函数地址:
f: 4294979808
f2: 4294980032
f3: 4294980080
f4: 4294980240

virtual 是如何实现的呢?

通过上面的理解和具体的demo结果可知,虚函数的实现是如何的,即通过虚指针和虚函数表来实现具体函数调用

虚析构函数的作用呢?

和刚刚开始说明的作用一样,就是为了解决这种父类指针指向子类对象时,对象销毁时调用子类的析构函数销毁数据避免内存泄露的产生

posted @ 2023-03-07 18:55  严_青  阅读(666)  评论(0编辑  收藏  举报