深入分析C++类中虚表用法

C++类中的虚表结构是C++对象模型中一个重要的知识点,这里咱们就来深入分析下虚表的在内存中的结构。

C++一个类中有虚函数的话就会有一个虚表指针,其指向对应的虚表,一般一个类只会有一个虚表,每个虚表有多个”插槽”,每个插槽存放一个虚函数的地址。插槽中的内容可以被覆盖,子类如果重写了父类中的虚函数,则插槽中对应位置的数据被覆盖。虚表存放的是虚函数地址,不管该虚函数是public还是private的。光文字说明不太形象,下面上一张虚表结构的示例图:

从图中看出,虚表指针确实是指向虚表结构的,这个虚表结构中有许多插槽,每个插槽都会指向一个虚函数。那么如何用程序来测试呢,请接着看:

#include <iostream>

#include <cstdio>

using namespace std;

class Base {

public:

virtual void test() {

cout << "Base.text()" << endl;

}

public:

int a;

};

class Derived : public Base {

public:

virtual void test() {

cout << "Derived.test()" << endl;

}

public:

int b;

};

typedef void (*PFunc)();

int main() {

Derived derived;

PFunc  ptest;  // 函数指针

// 输出derived及其成员a/b的地址

printf("derived:  %p\n", &derived);

printf("derived.a: %p\n", &(derived.a));

printf("derived.b: %p\n", &(derived.b));

// 提取出test虚函数地址

int *p = (int *)*(int *)(&derived);

ptest = (PFunc)*p;

ptest();

return 0;

}

输出结果:

注意,程序是在CentOS 7 64位系统下进行测试的。程序中直接提取出test虚函数地址,然后进行调用,发现调用的确实是Derived.test函数,这也说明了虚表结构的内存布局。

关于虚表几个有意思的问题

虚表指针什么时候赋值的?

#include <iostream>

using namespace std;

class Base

{

public:

Base() {

cout << "Base()" << endl;

show();

int *p = &b;

cout << "Base::b: " << p << endl;

p = (int *)((char *)p - 8);

cout << "Base::vptr: " << *p << endl;

// Base中虚函数地址

cout << "*Base::vptr: " << *(int *)*p << endl;

cout << endl;

}

virtual void show() {

cout << "Base::show()" << endl;

}

public:

int b;

};

class Derived : public Base

{

public:

Derived()

{

cout << "Derived()" << endl;

show();

int *p = &b;

cout << "Derived::b: " << p << endl;

p = (int *)((char *)p - 8);

cout << "Derived::vptr: " << *p << endl;

// Derived中虚函数地址

cout << "*Derived::vptr: " << *(int *)*p << endl;

cout << endl;

}

virtual void show() {

cout << "Derived::show()" << endl;

}

private:

int d;

};

int main(int argc, char **argv)

{

Base base;

Derived derived;

return 0;

}

从输出结果中可以得出,子类在构造过程中虚表指针会被赋值2次。初始化如下:

基类静态成员 – 子类静态成员 – (设置v_ptr/基类成员变量 ) –基类构造函数 – (设置v_ptr/子类成员变量) – 子类构造函数

在类的析构函数中是否对虚表指针进行赋值操作呢?

在子类的析构函数中,会把虚表指针设置为指向父类中的虚函数地址,这样在父类的析构函数中调用虚函数实际上调用的是父类的虚函数,不过一般不这样做。如何进行测试呢,按照上个问题的测试代码,然后稍微改动一下就可以测试了。

时间: 2024-09-20 07:59:28

深入分析C++类中虚表用法的相关文章

Java中StringUtils工具类的一些用法实例

  这篇文章主要介绍了Java中StringUtils工具类的一些用法实例,本文着重讲解了isEmpty和isBlank方法的使用,另外也讲解了trim.strip等方法的使用实例,需要的朋友可以参考下 StringUtils 方法的操作对象是 java.lang.String 类型的对象,是 JDK 提供的 String 类型操作方法的补充,并且是 null 安全的(即如果输入参数 String 为 null 则不会抛出 NullPointerException ,而是做了相应处理,例如,如果

Java中BigDecimal类的简单用法_java

本文实例讲述了Java中BigDecimal类的简单用法,是Java程序设计中非常实用的技巧,分享给大家供大家参考.具体用法分析如下: 一般来说,一提到Java里面的商业计算,我们都知道不能用float和double,因为他们无法进行精确计算.但是Java的设计者给编程人员提供了一个很有用的类BigDecimal,他可以完善float和double类无法进行精确计算的缺憾.BigDecimal类位于java.maths类包下.首先我们来看下如何构造一个BigDecimal对象.它的构造函数很多,

php类中的各种拦截器用法分析_php技巧

本文实例讲述了php类中的各种拦截器用法.分享给大家供大家参考.具体用法分析如下: 1.__get( $property ) 访问未定义的属性时调用 复制代码 代码如下: class lanjie  {      function __get($name)      {          echo $name." property not found! ";      }  }  $ob = new lanjie();  echo $ob->g; 当我们调用对象$ob未定义的属性

详解Java中的时区类TimeZone的用法_java

一.TimeZone 简介TimeZone 表示时区偏移量,也可以计算夏令时. 在操作 Date, Calendar等表示日期/时间的对象时,经常会用到TimeZone:因为不同的时区,时间不同. 下面说说TimeZone对象的 2种常用创建方式.1.获取默认的TimeZone对象使用方法: TimeZone tz = TimeZone.getDefault() 2.使用 getTimeZone(String id) 方法获取TimeZone对象使用方法: // 获取 "GMT+08:00&qu

Yii数据模型中rules类验证器用法分析_php实例

本文实例讲述了Yii数据模型中rules类验证器用法.分享给大家供大家参考,具体如下: public function rules() { return array( array('project_id, type_id, status_id, owner_id, requester_id,', 'numerical', 'integerOnly'=>true), array('name', 'length', 'max'=>256), array('description', 'length

C++类中的static和const用法实例教程_C 语言

static和const是C++程序设计中非常重要的概念,本文实例列举了C++类中的static和const的规则和用法.供大家参考借鉴.具体说明如下: 首先以代码用来举例说明.示例代码如下: class A { public: A():m(10) //const成员必须在构造函数的初始化构造列表中初始化 { q = 40; } void fun1()const { m++; //错误.const成员是常量,不能改变其值. n++; //正确.static变量n属于类,但是每个对象的函数都可以访

VC++中图像处理类CBitmap的用法_C 语言

VC++中图像处理类CBitmap的用法 class CBitmap : public CGdiObject { DECLARE_DYNAMIC(CBitmap) public: static CBitmap* PASCAL FromHandle(HBITMAP hBitmap); // Constructors CBitmap(); BOOL LoadBitmap(LPCTSTR lpszResourceName); BOOL LoadBitmap(UINT nIDResource); BOO

Yii数据模型中rules类验证器用法分析

本文实例讲述了Yii数据模型中rules类验证器用法.分享给大家供大家参考,具体如下: public function rules() { return array( array('project_id, type_id, status_id, owner_id, requester_id,', 'numerical', 'integerOnly'=>true), array('name', 'length', 'max'=>256), array('description', 'length

深入分析C++派生类中的保护成员继承_C 语言

protected 与 public 和 private 一样是用来声明成员的访问权限的.由protected声明的成员称为"受保护的成员",或简称"保护成员".从类的用户角度来看,保护成员等价于私有成员.但有一点与私有成员不同,保护成员可以被派生类的成员函数引用. 如果基类声明了私有成员,那么任何派生类都是不能访问它们的,若希望在派生类中能访问它们,应当把它们声明为保护成员.如果在一个类中声明了保护成员,就意味着该类可能要用作基类,在它的派生类中会访问这些成员.