C++虚函数表和虚析构介绍

目录
  • 1、虚函数表
  • 2、虚析构

1、虚函数表

虚函数表是C++实现多态的基础,多态是面向对象的三大特性之一,多态有利于提高代码的可读性,便于后期代码的扩展和维护。我们都知道多态的实现是基于虚函数表,那么虚函数表是什么时候创建的呢?虚函数表是怎么实现多态的功能的呢?

首先应该明确多态也称为动态多态,他是在程序运行时候确定函数地址的,也就是程序在运行时,如果类成员函数加了virtual关键字,就会建立一个虚函数指针(vfptr)指针指向一个虚函数表,这个虚函数表就保存了虚函数的地址,子类继承父类也自然继承了虚函数指针,当子类重写父类的虚函数时,虚函数指针所指向的虚函数表中的虚函数地址就会被覆盖,替换成子类的虚函数地址。也就是通过父类的虚函数指针找到了子类的虚函数地址,进而执行这个函数。

下面我们通过代码进行详细说明:

#include <iostream>
using namespace std;

class Base{
public:
    void func(){
        cout << "Base func" << endl;
    }
};

class Son: public Base{
    void func(){
        cout << "Son func" << endl;
    }
};

void test(Base& base) {
    base.func();
}
int main () {
    Son son;

    cout << "sizeof(Base) = " << sizeof(Base) << endl;
    cout << "sizeof(Son)  = " << sizeof(Son) << endl;
    test(son);
    system("pause");
    return 0;

}

代码运行结果为:

因为函数成员不占用类的大小,所以对Base类和Son类输出大小,都是一个字节,这一个字节是为了可以实例化类,通过引用基类引用派生类,调用func函数,函数调用了基类的func,那么如果我们加上virtual关键字后,就不是这种情况了。

#include <iostream>
using namespace std;

class Base{
public:
    virtual void func(){
        cout << "Base func" << endl;
    }
};

class Son: public Base{
    void func(){
        cout << "Son func" << endl;
    }
};

void test(Base& base) {
    base.func();
}
int main () {
    Son son;

    cout << "sizeof(Base) = " << sizeof(Base) << endl;
    cout << "sizeof(Son)  = " << sizeof(Son) << endl;
    test(son);
    system("pause");
    return 0;

}

代码运行结果为:

可以看到加了virtual关键字后,父类和子类的大小都变成了四字节,这是因为生成了虚函数指针,指针指向虚函数表,虚函数表存储了虚函数地址,继承了父类的子类重写了虚函数,虚函数表中的函数地址被替换,再次调用虚函数就是调用了子类的函数func

2、虚析构

虚析构主要是为了解决子类中有属性开辟到堆区,父类指针调用函数时,无法调用到子类的析构代码,导致子类堆区内存无法释放。

首先我们看一下子类堆区内存开辟,通过父类指针来调用函数,捕捉他们的构造函数和析构函数看下运行结果:

#include <iostream>
using namespace std;

class Base{
public:
    Base(){
        cout << "Base 的构造函数调用" << endl;
    }

    ~Base(){
        cout << "Base 的析构函数调用" << endl;
    }
    virtual void func(){
        cout << "Base func" << endl;
    }
};

class Son: public Base{
public:
    Son(int val):m_val(new int (val)) {
        cout << "Son 的构造函数调用" << endl;
    }

    ~Son(){
        cout << "Son 的析构函数调用" << endl;
        if (m_val != NULL) {

            delete m_val;
            cout << "Son 析构函数的堆内存释放" << endl;
            m_val = NULL;
        }
    }
    void func(){
        cout << "Son func" << endl;
    }

    void funcTest(){
        cout << "funcTest 函数调用" << endl;
    }

    int* m_val = NULL;
};

void test() {

    Base *base = new Son(10);
    base->func();
    //base->funcTest(); //无法调用,因为虚函数表中不能找到这个函数的地址
    delete base;
    base = NULL;
}

int main () {
    test();
    system("pause");
    return 0;

}

代码运行结果为:

可以明确,通过父类指针来调用函数的时候,无法调用Son类的析构函数,在Son类在堆区上申请的内存就无法释放,造成内存泄漏。Son类的析构函数不能调用的主要原因就是在虚函数表中找不到Son的析构函数地址,解决办法就是把Base类的写成虚析构函数或者纯虚析构函数,

下面给出Base类为纯虚析构函数的代码和运行结果:

#include <iostream>
using namespace std;

class Base{
public:
    Base(){
        cout << "Base 的构造函数调用" << endl;
    }
    virtual ~Base() = 0;

    virtual void func(){
        cout << "Base func" << endl;
    }
};
    Base :: ~Base(){
        cout << "Base 的析构函数调用" << endl;
    }

class Son: public Base{
public:
    Son(int val):m_val(new int (val)) {
        cout << "Son 的构造函数调用" << endl;
    }

    ~Son(){
        cout << "Son 的析构函数调用" << endl;
        if (m_val != NULL) {

            delete m_val;
            cout << "Son 析构函数的堆内存释放" << endl;
            m_val = NULL;
        }
    }
    void func(){
        cout << "Son func" << endl;
    }

    void funcTest(){
        cout << "funcTest 函数调用" << endl;
    }

    int* m_val = NULL;
};

void test() {

    Base *base = new Son(10);
    base->func();
    //base->funcTest(); //无法调用,因为虚函数表中不能找到这个函数的地址
    delete base;
    base = NULL;
}

int main () {
    test();
    system("pause");
    return 0;

}

代码运行结果为:

可以看到只要把Base类的析构函数写成虚析构函数或纯虚析构函数,通过父类指针调用函数,子类的析构代码会被调用,子类堆区内存得到释放。

到此这篇关于C++虚函数表和虚析构介绍的文章就介绍到这了,更多相关C++虚函数表和虚析构内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 聊一聊C++虚函数表的问题

    之前只是看过C++虚函数表相关介绍,今天有空就来写代码研究一下. 面向对象的编程语言有3大特性:封装.继承和多态.C++是面向对象的语言(与C语言主要区别),所以C++也拥有多态的特性. C++中多态分为两种:静态多态和动态多态. 静态多态为编译器在编译期间就可以根据函数名和参数等信息确定调用某个函数.静态多态主要体现为函数重载和运算符重载. 函数重载即类中定义多个同名成员函数,函数参数类型.参数个数和返回值不完全相同,编译器编译后这些同名函数的函数名会不一样,也就是说编译期间就确定了调用某个函

  • C++虚函数表深入研究

    目录 探索虚函数表结构 继承基类重写虚函数 多基类继承 虚函数表 寻找被覆盖的虚函数 总结 面向对象的编程语言有3大特性:封装.继承和多态.C++是面向对象的语言(与C语言主要区别),所以C++也拥有多态的特性. C++中多态分为两种:静态多态和动态多态. 静态多态为编译器在编译期间就可以根据函数名和参数等信息确定调用某个函数.静态多态主要体现为函数重载和运算符重载. 函数重载即类中定义多个同名成员函数,函数参数类型.参数个数和返回值不完全相同,编译器编译后这些同名函数的函数名会不一样,也就是说

  • c++虚函数与虚函数表原理

    目录 1.什么是虚函数? 2.虚函数会影响类的内存 3.了解虚函数表--->通过虚函数表的指针去访问数据 4.虚函数声明 1.什么是虚函数? 用virtual 修饰的成员函数叫虚函数 小知识: 没有虚构造函数        不写虚函数,没有默认的虚函数 普通函数不影响类的内存: class MM { public: void print() { cout << "普通函数"<< endl; //普通函数不影响类的内存<--->普通函数存在另一段

  • C++ 虚函数及虚函数表详解

    多态"的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定. #include <iostream> using namespace std; class A { public: int i; virtual void func() {} virtual void func2() {} }; class B : public A { int j; void func() {} }; int main() { cout <<

  • C++ 虚函数表图文解析

    一.前言 一直以来,对虚函数的理解仅仅是,在父类中定义虚函数,子类中可以重写该虚函数,并且父类指针可以指向子类对象,调用子类的虚函数(多态).在读研阶段经历的几个项目中,自己所写的类中并没有用到虚函数,对虚函数这个东西的强大之处并没有太多体会.最近,学了设计模式中的简单工厂模式,对多态有了具体的认识.于是,补了补多态.虚函数.虚函数表相关的知识,参考相关博客,加上自己的理解,整理了这篇博文. 二.含有虚函数类的内存模型 以下面的类为例(32位平台下): class Father { public

  • C++虚函数表和虚析构介绍

    目录 1.虚函数表 2.虚析构 1.虚函数表 虚函数表是C++实现多态的基础,多态是面向对象的三大特性之一,多态有利于提高代码的可读性,便于后期代码的扩展和维护.我们都知道多态的实现是基于虚函数表,那么虚函数表是什么时候创建的呢?虚函数表是怎么实现多态的功能的呢? 首先应该明确多态也称为动态多态,他是在程序运行时候确定函数地址的,也就是程序在运行时,如果类成员函数加了virtual关键字,就会建立一个虚函数指针(vfptr)指针指向一个虚函数表,这个虚函数表就保存了虚函数的地址,子类继承父类也自

  • C++ 中的虚函数表及虚函数执行原理详解

    为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚函数的类(或者继承自的类包含了虚函数)都有一个自己的虚函数表.这个表是一个在编译时确定的静态数组.虚函数表包含了指向每个虚函数的函数指针以供类对象调用. 其次,编译器还在基类中定义了一个隐藏指针,我们称为 *__vptr,*__vptr 是在类实例创建时自动设置的,以指向类的虚函数表.*__vptr

  • C++虚函数表与类的内存分布深入分析理解

    目录 不可定义为虚函数的函数 将析构函数定义为虚函数的作用 虚函数表原理 继承关系中虚函数表结构 多重继承的虚函数表 多态调用原理 对齐和补齐规则 为什么要有对齐和补齐 资源链接 不可定义为虚函数的函数 类的静态函数和构造函数不可以定义为虚函数: 静态函数的目的是通过类名+函数名访问类的static变量,或者通过对象调用staic函数实现对static成员变量的读写,要求内存中只有一份数据.而虚函数在子类中重写,并且通过多态机制实现动态调用,在内存中需要保存不同的重写版本. 构造函数的作用是构造

  • C++对象内存分布详解(包括字节对齐和虚函数表)

    1.C++对象的内存分布和虚函数表: C++对象的内存分布和虚函数表注意,对象中保存的是虚函数表指针,而不是虚函数表,虚函数表在编译阶段就已经生成,同类的不同对象中的虚函数指针指向同一个虚函数表,不同类对象的虚函数指针指向不同虚函数表. 2.何时进行动态绑定: (1)每个类对象在被构造时不用去关心是否有其他类从自己派生,也不需要关心自己是否从其他类派生,只要按照一个统一的流程:在自身的构造函数执行之前把自己所属类(即当前构造函数所属的类)的虚函数表的地址绑定到当前对象上(一般是保存在对象内存空间

  • 虚函数表-C++多态的实现原理解析

    参考:http://c.biancheng.net/view/267.html 1.说明 我们都知道多态指的是父类的指针在运行中指向子类,那么它的实现原理是什么呢?答案是虚函数表 在 关于virtual 一文中,我们详细了解了C++多态的使用方式,我们知道没有 virtual 关键子就没法使用多态 2.虚函数表 我们看一下下面的代码 class A { public: int i; virtual void func() { cout << "A func" <<

  • C++虚函数表的原理与使用解析

    目录 前言 1.虚函数表 2.一般继承(无虚函数覆盖) 3.一般继承(有虚函数覆盖) 4.多重继承(无虚函数覆盖) 5.多重继承(有虚函数覆盖) 6.安全性 6.1 通过父类型的指针访问子类自己的虚函数 6.2 访问non-public的虚函数 7.结束语 7.1 VC中查看虚函数表 7.2 例程 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛

  • C++ 类中有虚函数(虚函数表)时 内存分布详解

    虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数.这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数. 这里我们着重看一下这张虚函数表.C++的编译器应该是

随机推荐