C++多态实现的基本是虚函数,虚函数实现的依赖的机制是指针,即在类中使用指针变量向一片内存区域。后面知道这个内存区域存放是函数指针列表。所以使用函数指针数组的方式来实现。
理解虚指针,虚函数表
在C语言中我们使用struct创建结构体,应用结构体时都使用到一个概念叫内存首地址,内存首地址也是内存区域第一个元素地址。数组中使用数组名来作为数组内存首地址,可以使用数组名作为指针操作数组中的数据。在类中也是一样,我们使用一个变量,使它固定到类第一个位置,那么不管我们如何作指针转换也可以通过类名访问到,因为不管如何作指针强制转换,首地址是不变的。那么这样一个地址就可以被拿来作为共用内存区域用来存放函数指针,这个地址就是虚指针。
由于基类、派生类都共用首地址内存区域,多态的指针强制转换时,都可以访问到这个内存(强制转换都指向首地址,后面有示列)。首地址只有一个为了实现更多的功能,便把这个区域存放了指针,指向其它更大内存区域,或说更灵活的内存区域(虚函数表区域)。类的其它内存区域都是相对地址,访问起来不方便。
从理论上来说,首地址也可以访问到很大内存,比如不把它定为一个指针而是定义成一个结构体或类也是可以,但都不灵活。指针的话,你想指向哪片内存都可以,而指向的那片内存更是灵活的,想怎么用怎么用。只是这里作为了函数指针存放的区域(虚指针表)。
从上我们可以推断出,虚指针变量的类型为 void**,第一次解引用获得指向的虚函数表内存首地址,再次解引用获得虚数表内容,即函数。
示列
先给定函数指针: typedef void (Func)(); //函数类型为 void ()()
我们实例化一个obj类,这个类里有虚函数,我们通过类名来访问虚函数:
第一步:(unsigned log*)obj (把obj转化成指针,指针所占字节依使用硬件架构(32位int*, 64 long*))
第二步:(unsigned long)obj(获得类首地址指针变量值,即指针指向的虚函数表内存首地址值。)
第三步:((unsigned long**)(unsigned long)obj) (指针变量的值,需要给定类型才到内存中的数据,所以再次指针类型转化)
第四步:有2种处理方式,一种是使用数组的方式来访问虚函数指针,另外一种是以指针偏移的方式来访问虚函数表。
//offset从虚函数表第几个开始访问,使用for遍历所有虚函数,i作为指针偏移量。
Fun getAddr(void* obj, unsigned int offset)
{
Fun f = nullptr;
for(int i = 0; i < size; i++ ){
f = (Fun)((unsigned long**)(*(unsigned long*)obj))[offset + i];
(*f)();
}
return f;
}
指针偏移方式:
f = (Fun)*((unsigned long**)(*(unsigned long*)obj) + offset + i);
具体使用下面给出示列
测试这个代码时有些问题,就是使用指针访问虚函数时,如果在虚函数中读取类中的成员变量,会出错(但第一个虚函数读取是正确认的,后面都是失败)。打印地址查看,并不是类成员的变量地址。具体原因不明,可能是编译器在编译时加了很多确认类型的信息,使用指针的方便跳过了很多flag信息,导致访问不到类成员变量。所以说直接这个指针访问是不安全操作,最好还是使用对象来操作。
#include <iostream>
#include <string>
#include <string.h>
#include <thread>
using namespace std;
const int size = 3;
typedef void (*Fun)();
class Base {
public:
virtual void aFunc() const {
cout << "Base virtual afunc:"<< endl; }
virtual void bFunc() const {
cout << "Base virtual bfunc" << endl; }
virtual void cFunc() const {
cout << "Base virtual cfunc" << endl; }
void dFunc() const {
cout << "Base dfunc" << endl; }
void eFunc() const {
cout << "Base efunc" << endl; }
Base(){
};
~Base(){
};
private:
int age_1 = 10;
};
class Base2{
public:
void showFunc()const {
cout << "Base_2 showFunc" << endl;
}
};
class Device: public Base, public Base2 {
public:
void aFunc() const {
cout << "Dev virtual afunc:" << age << endl;}
void cFunc() const {
cout << "Dev virtual cfunc:" << age << endl; }
virtual void gFunc() const {
cout << "Dev virtual gfunc:" << age << endl; }
void hFunc() {
cout << "Dev hfunc:" << age << endl; }
Device(){
printf("the DEV a addr:%p \n", &age); }
~Device(){
}
private:
int age = 110;
};
Fun getAddr(void* obj, unsigned int offset)
{
Fun f = nullptr;
for(int i = 0; i < size; i++ ){
f = (Fun)((unsigned long**)(*(unsigned long*)obj))[offset + i];
(*f)();
}
return f;
}
int main(int argc, char** argv)
{
Base* bs1 = new Base();
Device* bs2 = new Device();
Base* bs3 = new Device();
Base* b =(Base*)bs3;
Base2* b1 =(Base2*)bs3;
printf("The Device transfor Base addr:%p \n", b);
printf("The Device transfor Base_2 addr:%p \n", b1);
bs1->aFunc();
bs1->dFunc();
puts("---------Device---------");
bs2->aFunc();
bs2->dFunc();
bs2->cFunc();
bs2->hFunc();
puts("---------Device to Base transfor---------");
bs3->aFunc();
bs3->dFunc(); //Base member func.
//bs3->hFunc(); 'class Base' has no member named 'hFunc'; did you mean 'aFunc'?
puts("---------Device to Base and Base2----------");
b->aFunc();
b->dFunc(); //Base member func.
b1->showFunc();
//b1->aFunc(); 'class Base2' has no member named 'aFunc'; did you mean 'showFunc'?
puts("---------get addr print----------");
getAddr(bs3, 0);
return 0;
}
运行结果
the DEV a addr:0x55963b564e9c
the DEV a addr:0x55963b5652cc
The Device transfor Base addr:0x55963b5652c0
The Device transfor Base_2 addr:0x55963b5652c0
Base virtual afunc:
Base dfunc
---------Device---------
Dev virtual afunc:110
Base dfunc
Dev virtual cfunc:110
Dev hfunc:110
---------Device to Base transfor---------
Dev virtual afunc:110
Base dfunc
---------Device to Base and Base2----------
Dev virtual afunc:110
Base dfunc
Base_2 showFunc
---------get addr print----------
Dev virtual afunc:110 //访问成员正确
Base virtual bfunc
Dev virtual cfunc:21910 //访问类成员数据失败
不理解:指针类型转换后,只能访问到对应类型的函数。按理拿到首地址应该整个类都访问的到,可能这就是为什么C++只能通过虚函数方式来实现多态吧。
以上个人理解心得,如有不正确认的地方欢迎指正,以上内容仅供参考。