C++对象模型和虚函数表分析以及重载、重写、隐藏的区别
来源:互联网 发布:数据库保留两位小数 编辑:程序博客网 时间:2024/06/05 17:19
C++在布局以及存取时间上主要的额外负担是由virtual引起的。包括:
- Virtual function 机制 。用以支持一个有效率的“执行期绑定”(runtime binding)。
- Virtual base class。用以实现“多次出现在继承体系中的base class,有一个单一而共享的实体”。
C++对象模式:
在C++中,有两种class data members : static 和 nonstatic
有三种class member functions: static 、nonstatic 和 virtual
C++模型的演变的过程如下:
简单对象模型(Simple Object Model)
表格驱动对象模型(Member Table Object Model)
该模型将所有的与members相关的信息抽出来,放在一个 data member table 和一个 member function table 之中,class object 本身则含有指向这两个表格的指针。Member function table 是一系列的 slots,每一个 slot 指出一个 member function;Data member table 则直接含有 data 本身。
所以这里相当于 class 指向了一个 Funciton Member Table,Function Member Table 中的每个 slot 又指向了函数地址,多了一层访问的间接性。
C++对象模型(The C++ Object Model)
此模型中,Nostatic data members 被配置于每一个 class object 之内,static data members 也被放在所有的 class object 之外, Virtual function 则以两个步骤支持之。
- 每一个 class 产生出一堆指向 virtual functions 的指针,放在表格之中,这个表格被称为 virtual table(vtbl)。
- 每一个 class object 被添加了一个指针,指向相关的 virtual table 。通常这个指针被称为 vptr,vptr 的设定(setting)和重置(resetting)都由每一个 class 的constructor、destructor 和 copy assignment 运算符自动完成,每一个 class 所关联的 type_info object (用以支持 runtime type identigication,RTTI)也经由 virtual table 被指出来,通常是放在表格的第一个 slot 处。
对象的差异(An Object Distinction)
程序模型(procedural model)、抽象数据类型模型(ADT),面向对象模型(object-oriented model)
class base {public: base() = default; virtual void foo() { std::cout<<"base"<<std::endl; } virtual ~base() {}private: int int_;};class derived : public base {public: derived() = default; ~derived() {} void foo() { std::cout<<"derived"<<std::endl; }private: int int_;};//void do_polymorphic(base ptr) //输出:basevoid do_polymorphic(base& ptr) //引用或指针将输出:derived{ ptr.foo();}int main(){ derived dr; do_polymorphic(dr); return 0;}上述程序中,使用父类类型接收子类,将会发生切片,会调用父类自己的 foo() 函数。对于 object 的多态操作,object 必须可以经由一个 pointer 或 referrnce 来存取。
1.经由一组隐含的转化操作。例如把一个 derived class 指针转化为一个指向其 public base type 的指针:
shape *ps = new circle();
2.经由 virtual function 机制:
3.经由 dynamic_cast 和 typeid 运算符:ps->rotate();
if ( circle * pc = dynamic_cast< circle* > ( ps ) ) ...
多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象 base class 中。可以在执行期根据 object 的真正类型解析出到底是哪一个函数实体被调用。
需要多少内存才能够表现一个 class object ? 一般而言要有:
- 其 nonstatic data members 的总和大小
- 加上任何由于 alignment(alignment 就是将数值调整到某数的倍数。在 32 位计算机上,通常 alignment 为 4 bytes(32位),以使 bus 的“运输量”达到最高效率)的需求而填补(padding)上去的空间(可能存在于 members 之间,也可能存在于集合体边界)
- 加上为了支持 virtual 而由内部产生的任何额外负担(overhead)
Bear b; ZooAnimal *pz = &b; Bear *pb = &b;
- ( ( Bear *) pz )->cell_block // ok,经过一个明白的 down_cast就没问题
- if ( Bear* pb2 = dynamic_cast< Bear* >( pz ) ) pz->cell_block; //这样更好,但它是一个 run-time operation,成本较高
#include <iostream>#include <stdio.h>class base {public: base(int i) : int_base_(i) {} virtual void foo() { std::cout<<"base"<<std::endl; } virtual ~base() = default;protected: int int_base_; //data member also can be overrided};class derived : public base {public: derived(int i) : int_(i), base(4) { //this->int_base_ = 4; //std::cout<<this->int_base_<<std::endl; } ~derived() = default; void foo() { std::cout<<"derived"<<std::endl; }private: int int_;};void do_polymorphic(base* ptr){ ptr->foo();}int main(){ derived dr(5); base b(1); void *ptr = static_cast<void *>(&dr); ( (void(*)()) (*(int *)(*((int *)ptr))) )(); //调用derived的虚函数表中对应的第一个虚函数 //typedef void(*fun)(); //这种方式是上面的简化版 //fun f = fun( (*(int *)(*((int *)ptr))) ); //f(); printf("%x\n", *(int *)ptr); void *base_ptr = static_cast<void *>(&b); printf("%x\n", *(int *)base_ptr); ( (void(*)()) (*(int *)(*((int *)base_ptr))) )(); return 0;}注意点有以下几点:
- ( void (*)() )X,左边void (*)()类似于上面的fun,用来强制转化X为函数指针类型,然后代码中右侧再加 (),构成函数调用 ;而 ( void )(* X)(),代表X的返回值为 void() 类型,此处X为表示所用符号,不代表X就是一个整型变量。
- 注意 override 重写(重写=覆盖),如果子类重写了父类的虚函数 fun(),重写就是所有参数都一样,那么子类调用将调用自己的 fun() 函数,如果没有,会调用父类的 fun() 函数。
- 子类在构造函数时,如果父类构造函数不是 default,应该在成员初始化列表中对父类进行显示初始化。当然使用this->的方式也可以,不过不推荐。
- 父类和子类的虚函数表不是共用的,子类继承时会首先拷贝一份父类的 virtual table,如果子类对某个 virtual function 进行了 override,那么子类会在自己的 virtable 中该 virtual function 所在位置写上自己的新函数的地址。
- 相同的范围(在同一个类中)
- 函数名字相同
- 参数不同
- virtual关键字可有可无
- 不同的范围,分别位于基类和派生类中
- 函数的名字相同
- 参数相同
- 基类函数必须有virtual关键字
- 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
- 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。
- C++对象模型和虚函数表分析以及重载、重写、隐藏的区别
- C+=重载、重写和隐藏的区别以及实例分析
- C++中函数重载、隐藏、覆盖和重写的区别
- C++_重载、重写(覆盖)和隐藏的区别:
- C++中重载、重写(覆盖)和隐藏的区别实例分析(重写有修改)
- [C++]函数的重载、重写和重定义的区别
- 函数的重载,重写,隐藏
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- 实例分析C++中重载、重写(覆盖)和隐藏的区别
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- C++中重载、重写(覆盖)和隐藏的区别实例分析
- Jackson(二) Databind
- 其他数制与十进制转换
- Chrome插件(UserScript)开发教程
- 复习(1)-- C程序的内存分布
- 购物车3种实现方式 详解
- C++对象模型和虚函数表分析以及重载、重写、隐藏的区别
- COM技术
- 关于IllegalMonitorStateException异常的解释
- 数据结构之打印一棵二叉树
- 苏联俄罗斯文学 —— 陀思妥耶夫斯基、契科夫
- Hibernate
- 【腾讯TMQ】带你寻找谷歌的bug
- 海量数据优化查询
- PHP命名空间(Namespace)的使用详解