C++对象模型
2015-08-25 16:54
501 查看
本文主要对C++对象模型做一个简单总结。主要讨论以下几种情况下的C++对象的内存布局情况。
1) 单一的一般继承
2) 单一的虚拟继承
3) 多重继承
4) 重复多重继承
5) 钻石型的虚拟多重继承
虚函数
先简单介绍一下虚函数的机制。虚函数的主要作用是实现了多态的机制。对于多态,简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。从而让父类的指针有“多种形态”,这是一种泛型技术。
都知道虚函数是通过一张虚表来实现的,在这个表中主要是一个类的虚函数的地址列表。在有虚函数的类的实例中这个表就被分配在这个实例的内存中,它就像一个地图,指向实际应该调用的函数。为了保证取到虚函数表的有最高的性能,C++编译器一般会保证虚函数表的指针存放于对象实例中最前面的位置。这意味着我们可以通过对象实例的地址来获得这张虚函数表,从而遍历其中所有的函数指针,并调用。
下面给出一个实际的例子。
我们可以通过Base的实例来得到虚函数表,使用如下代码:
运行结果如下:
我们强行把&b转成intptr_t *,取得虚函数表指针的地址,然后再次取址就得到第一个虚函数的地址了,即Base::f()。同样的,我们如果要调用Base::g()和Base::h(),可以使用如下代码:
下图可以帮助理解:
注意:上面这个图中,虚函数表的最后有一个点,这个是虚函数表的结束点。这个值在不同的编译器下是不同的。在win8.1+vs2013中,这个值是NULL。在Ubuntu 14.04+GCC4.8.2中,如果这个值为1,表示还有下一个虚函数表(多重继承),如果值是0,则表示是最后一个虚函数表。
单一的一般继承
下面,我们假设有如下一种继承关系,父类、子类、子类的子类都有自己的一个成员变量。子类覆盖了父类的f()方法,子类的子类覆盖了子类的g_child()及f()。源代码如下:
我们使用以下程序作为测试程序:
运行结果如下:
使用图片表示如下:
可以看出:
1)虚函数表指针在对象内存的起始位置
2)成员变量根据其继承和声明的顺序依次排在后面
3)在单一继承中,被override的虚函数在虚函数表中得到了更新
多重继承
(未完待续)
1) 单一的一般继承
2) 单一的虚拟继承
3) 多重继承
4) 重复多重继承
5) 钻石型的虚拟多重继承
虚函数
先简单介绍一下虚函数的机制。虚函数的主要作用是实现了多态的机制。对于多态,简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。从而让父类的指针有“多种形态”,这是一种泛型技术。
都知道虚函数是通过一张虚表来实现的,在这个表中主要是一个类的虚函数的地址列表。在有虚函数的类的实例中这个表就被分配在这个实例的内存中,它就像一个地图,指向实际应该调用的函数。为了保证取到虚函数表的有最高的性能,C++编译器一般会保证虚函数表的指针存放于对象实例中最前面的位置。这意味着我们可以通过对象实例的地址来获得这张虚函数表,从而遍历其中所有的函数指针,并调用。
下面给出一个实际的例子。
class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } };
我们可以通过Base的实例来得到虚函数表,使用如下代码:
typedef void(*Fun)(void); Base b; Fun pFun = NULL; cout << "虚函数表指针的地址:" << (intptr_t*)(&b) << endl; cout << "虚函数表 — 第一个函数地址:" << (intptr_t*)*(intptr_t*)(&b) << endl; // Invoke the first virtual function pFun = (Fun)*((intptr_t*)*(intptr_t*)(&b)); pFun();
运行结果如下:
我们强行把&b转成intptr_t *,取得虚函数表指针的地址,然后再次取址就得到第一个虚函数的地址了,即Base::f()。同样的,我们如果要调用Base::g()和Base::h(),可以使用如下代码:
(Fun)*((intptr_t*)*(intptr_t*)(&b)+0); // Base::f() (Fun)*((intptr_t*)*(intptr_t*)(&b)+1); // Base::g() (Fun)*((intptr_t*)*(intptr_t*)(&b)+2); // Base::h()
下图可以帮助理解:
注意:上面这个图中,虚函数表的最后有一个点,这个是虚函数表的结束点。这个值在不同的编译器下是不同的。在win8.1+vs2013中,这个值是NULL。在Ubuntu 14.04+GCC4.8.2中,如果这个值为1,表示还有下一个虚函数表(多重继承),如果值是0,则表示是最后一个虚函数表。
单一的一般继承
下面,我们假设有如下一种继承关系,父类、子类、子类的子类都有自己的一个成员变量。子类覆盖了父类的f()方法,子类的子类覆盖了子类的g_child()及f()。源代码如下:
class Parent { public: int iparent; Parent ():iparent (10) {} virtual void f() { cout << " Parent::f()" << endl; } virtual void g() { cout << " Parent::g()" << endl; } virtual void h() { cout << " Parent::h()" << endl; } }; class Child : public Parent { public: int ichild; Child():ichild(100) {} virtual void f() { cout << "Child::f()" << endl; } virtual void g_child() { cout << "Child::g_child()" << endl; } virtual void h_child() { cout << "Child::h_child()" << endl; } }; class GrandChild : public Child{ public: int igrandchild; GrandChild():igrandchild(1000) {} virtual void f() { cout << "GrandChild::f()" << endl; } virtual void g_child() { cout << "GrandChild::g_child()" << endl; } virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; } };
我们使用以下程序作为测试程序:
typedef void(*Fun)(void); Fun pFun = NULL; GrandChild gc; intptr_t** pVtab = (intptr_t**)&gc; cout << "[0] GrandChild::_vptr->" << endl; for (int i = 0; (Fun)pVtab[0][i] != NULL; i++){ pFun = (Fun)pVtab[0][i]; cout << " [" << i << "] "; pFun(); } int *pData = (int *)((intptr_t *)&gc + 1); cout << "[1] Parent.iparent = " << (int)pData[0] << endl; cout << "[2] Child.ichild = " << (int)pData[1] << endl; cout << "[3] GrandChild.igrandchild = " << (int)pData[2] << endl;
运行结果如下:
使用图片表示如下:
可以看出:
1)虚函数表指针在对象内存的起始位置
2)成员变量根据其继承和声明的顺序依次排在后面
3)在单一继承中,被override的虚函数在虚函数表中得到了更新
多重继承
(未完待续)
相关文章推荐
- C++类对象创建过程揭密
- 侯捷推荐c++书目
- 黑马程序员——C语言中的源程序
- 缓冲区的C++封装
- C语言求质数
- C语言学习-----指针篇(1)-----指向多维数组的指针和指针变量
- House Robber
- c++11新特性 auto的使用
- 最短路径算法—Bellman-Ford(贝尔曼-福特)算法分析与实现(C/C++)
- 最短路径算法—SPFA(Shortest Path Faster Algorithm)算法分析与实现(C/C++)
- c++基础1:需要了解的几点重要概念
- 《算法导论》中的计数排序的C++实现
- Item 10:赋值运算符要返回自己的引用 Effective C++笔记
- Item 9:在析构/构造时不要调用虚函数 Effective C++笔记
- C/C++中计算程序运行时间
- C++ 11开发环境的搭建(Windows Platform)
- 【c++】猜单词游戏
- Des算法的实现
- 排序算法之插入排序(C语言实现)
- c语言与c++区别