虚函数原理

虚函数表的数量与位置:编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。编译器将虚函数表存放在了目标文件或者可执行文件的常量段,即代码区。

虚函数表指针(vptr)的数量与位置:如果1个类中存在一个虚函数,那么第一个地址永远都是指向虚函数列表的指针。子类没有vptr,子类的虚函数存放在第一个父类的虚函数表的最后,如果有覆盖,则覆盖掉相应父类的虚函数。

lass Base {
public:
  virtual void f() { cout << "Base::f" << endl; }
  virtual void g() { cout << "Base::g" << endl; }
  virtual void h() { cout << "Base::h" << endl; }
};

Base b;

虚函数表地址:(int*)(&b)

虚函数表 第一个函数地址,即f()地址: (int*)*(int*)(&b)

g()地址:(int*)*(int*)(&b)+1

h()地址:(int*)*(int*)(&b)+2

注意:在虚函数表的最后有一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。

一般继承(无虚函数覆盖)

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

对于实例:Derive d; 的虚函数表如下:

一般继承(有虚函数覆盖)

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();
b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

多重继承(无虚函数覆盖)

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中,子类自己没有虚函数列表的指针。(所谓的第一个父类是按照声明顺序来判断的)

对于子类实例中的虚函数表,是下面这个样子:

多重继承(有虚函数覆盖)

三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。

下面是对于子类实例中的虚函数表的图:

虚析构函数的作用

class A
{
public:
  A(){cout << "In A constructor" << endl;}
  ~A(){cout << "In A destructor" << endl;}
};

class B : public A
{
public:
  B()
  {
    cout << "In B constructor" << endl;
    m_p = new char[10];
  }
  ~B()
  {
    cout << "In B destructor" << endl;
    if (m_p) delete [] m_p;
  }
private:
  char *m_p;
};

int main(int argc, char* argv[])
{
  A *p = new B;
  delete p;
  return 0;
}

输出结果:

In A constructor

In B constructor

In A destructor

并没有调用B的析构函数,new出来的内存没有及时回收造成内存泄漏。解决的方法是将~A()定义为虚析构函数,那么像其它虚构函数一样,~B()重定义(overridden)了~A(),这样指向派生类的指针就能根据运行时的状态调用B的析构函数了。这里又有一个问题:为什么还会调用A的析构函数呢?我只能理解为析构函数是一个特殊的函数,由系统维护其机制。

所以使用虚析构函数的目的是:为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

时间: 2024-10-20 08:19:53

虚函数原理的相关文章

虚函数原理解析

虚函数原理 虚函数的一般实现模型:每个类有一个虚函数表,内含该类中有作用的虚函数地址.每个 对象有一个vptr(虚函数表指针)指向虚函数表 如下Person类 class Person { public: virtual ~Person(); virtual string& getName(); virtual string& setName(); protected: string name_; }; 在Person的对象Jack中,有两个东西,一个是数据成员name_,一个是_Vptr

C++拾遗--虚函数原理

C++拾遗--虚函数原理 前言 C++的多态依赖虚函数来实现.若类存在虚函数,则每一个类的实例都维护了一个地址,这个地址指向虚函数表.虚函数表中存放的是类中所有虚函数的地址.下面我们找出虚函数表的地址,从而获得每个虚函数的地址,然后使用地址直接调用虚函数. 正文 1.空类的size #include <iostream> using namespace std; class MyClass { }; int main() { cout << "sizeof(MyClass

【C/C++学院】0823-静态联合编译与动态联合编译/父类指针子类指针释放/虚函数/纯虚函数概念以及虚析构函数/抽象类与纯虚函数以及应用/虚函数原理/虚函数分层以及异质链表/类模板的概念以及应用

静态联合编译与动态联合编译 #include <iostream> #include <stdlib.h> //散列 void go(int num) { } void go(char *str) { } //class //::在一个类中 class A { public: void go(int num) { } void go(char *str) { } }; void main() { ///auto p = go;编译的阶段,静态联编 void(*p1)(char *s

C++虚函数原理

类中的成员函数分为静态成员函数和非静态成员函数,而非静态成员函数又分为普通函数和虚函数. Q: 为什么使用虚函数 A: 使用虚函数,我们可以获得良好的可扩展性.在一个设计比较好的面向对象程序中,大多数函数都是与基类的接口进行通信.因为使用基类接口时,调用基类接口的程序不需要改变就可以适应新类.如果用户想添加新功能,他就可以从基类继承并添加相应的新功能. Q: 简述C++虚函数作用及底层实现原理 A: 要点是要答出虚函数表和虚函数表指针的作用. C++中虚函数使用虚函数表和虚函数表指针实现,虚函数

C++基础篇--虚函数原理

虚函数算是C++最关键和核心的内容之一,是组件的基础.下面先列出一些相关名词,再围绕它们举例说明虚函数的本质实现原理. 基础概念(英文部分来自C++编程思想) 1)绑定:Connectinga function call to a function body is called binding.(把函数调用和函数实现关联的过程) 2)早绑定:Whenbinding is performed before the program is run (by the compiler and linker

c++ 深入理解虚函数

为什么使用虚函数?什么是虚函数?虚函数是为了解决什么问题? 面向对象的三大特征: 封装 多态 继承 普通虚函数 虚析构函数 纯虚函数 抽象类 接口类 隐藏 vs 覆盖 隐藏与覆盖之间的关系 早绑定和晚绑定 虚函数表 什么是多态? 相同对象收到不同消息或不同对象收到相同消息时产生的不同的动作. 静态多态 vs 动态多态 [-:>静态多态也叫做早绑定 class Rect //矩形类 { public: int calcArea(int width); int calcArea(int width,

类虚函数表原理实现分析(当我们将虚表地址[n]中的函数替换,那么虚函数的实现就由我们来控制了)

原理分析 当调用一个虚函数时, 编译器生成的代码会调用 虚表地址[0](param1, param2)这样的函数. 已经不是在调用函数名了. 当我们将虚表地址[n]中的函数实现改为另外的函数, 虚函数的实现就由我们来控制了. 实验 根据虚表原理, 实验一下修改自己程序的虚函数表项地址. 使编译器生成的代码执行一个虚函数A时, 执行的是我们自己定义的非虚函数B. 知识点 * 使用union赋值, 绕过编译器函数与变量强转赋值的限制 * 类成员函数指针的执行 * 修改和恢复自己的代码段属性 * 虚函

虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

#include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout << "A:A" <<endl; } virtual void getb(){ cout << "A:B" <<endl; } }; class B :public A{ public: B(){} virtual void g

在类有成员变量的场景下, 按照虚表原理, 模拟虚函数实现

前言 当类没有成员变量的情况下,   类首地址有4个字节的空间, 这里可以放我们模拟出来的虚表入口地址. 当类有成员变量的情况下, 类首地址就是成员变量,  所以, 为了模拟虚表实现, 需要在成员变量前, 再定义一个int型变量, 用来存放模拟的虚表入口地址. 现在还得不到虚析构函数的地址, 暂时按照非虚析构函数进行模拟. 这个实验是在C++中模拟的. 模拟虚函数实现的用途 在非OOP语言(C语言)中, 模拟类的实现, 可以实现虚函数的效果. 效果 工程下载点 编译环境: vc6sp6 + wi