c++多态对象模型:单继承,多继承
2017-11-14 16:11
483 查看
要理解对象模型,首先需要理解多态是什么?
虚函数重写:子类中定义了一个和父类完全相同的虚函数。(覆盖)
举一个简单的例子:很多买票的场景,成人需要买全票,但是学生只用买半票,这就有了重写虚函数的必要。
可以看到子类中定义了一个和父类完全相同的虚函数,构成了重写。
func函数的作用是,使用父类的指针调用重写的虚函数,当传p的时候,毫无疑问,调用了父类的虚函数,但是当传s的时候,调用的却是子类的虚函数,这是怎么回事呢?
这就是多态!
这里需要说明三点:
1.去掉父类的virtual关键字,则不构成多态,也就是与类型有关。
2.去掉子类的virtual关键字,依然构成多态,我理解为编译器的默认。
3.当父类的返回值和子类的返回值不同(一个是void,一个是int)时,编译器报错:
重写虚函数返回类型有差异,且不是来自“Person::Buy”的协变
那么什么是协变呢,简单来说,就是虚函数的重写:函数名必须相同,参数必须相同,返回值可以不同(必须是父子关系的指针/引用):
这里的返回值构成了协变的条件,所以不会报错。
这里容易混淆重载,重定义和重写,总结一下:
注意:父类的析构函数最好定义为虚函数。
本来应该调用B的析构,但是没有,因为没有构成多态,与类型有关,若加上virtual,按上文所说,构成了虚函数得重写。
此时构成了多态:基类的指针调用重写的虚函数。
可以看出,Base类中有两个虚函数和一个普通函数。
按照以前的知识,sizeof(Base)应该是4,因为函数放在公共区域,但是它的大小是8字节,怎么回事?来看
一看Base的布局情况:Base b1;
数组指针_vfptr与_a的总大小为8,这样就解释了原因,但是我觉得还需要深层探索:
查看内存:
对照内存,我得出了模型:
Derive继承了Base类,并且重写了func1,而且有两个自己的虚函数。
查看布局:
但是这里只存了两个虚函数,剩下的func3和func3呢?
首先把_a,_b分别赋值为1,2,方便查看内存。
对比地址,看似已经得到了func3和func4的地址,但是有人告诉我说是假地址,那我可以打印地址来得到真地址:
这段代码其实很简单,取d的地址的前四个字节(64位下是前8个)传给Print函数,然后以4字节(64位下是8字节)为步数,打印指向的内容,结果如下:
注:地址和刚才不一样是因为清理了解决方案,这样得到了监视窗口显示不出来的func3和func4的真地址。
func3是普通函数,那么调用不同函数和调用虚函数有什么不同呢?
来从汇编角度看:
可以说,动态联编就是指针/引用+虚函数
多态就是动态联编+虚函数的重写
可以看到,这是一个多继承,Bass1和Bass2分别有两个虚函数func1和fun2,Derive继承了Bass1和Bass2,并且重写了func1,自己有一个虚函数func3。
接下来看Derive的布局:Derive d;
它有两个虚表,分别是两个父类的,存的是func1,func2,同样的,看不到func3,需要调用刚才写的打印函数:
首先打印Bass1的虚表:
Print((int**)(((int*)&d)));
可以看到,func3在Bass1的虚表里。
再来打印Bass2的虚表:
Print((int**)(((int*) ( (char*)&d+sizeof(Bass1) ) )));//第二个虚表需要往下移sizeof(Bass1)个字节。
这样,显而易见,func3存在了Bass1的虚表里,并且同时覆盖了两个父类的重写的虚函数。
多继承模型:
一:多态
虚函数:类的成员函数前加virtual虚函数重写:子类中定义了一个和父类完全相同的虚函数。(覆盖)
举一个简单的例子:很多买票的场景,成人需要买全票,但是学生只用买半票,这就有了重写虚函数的必要。
class Person { public: virtual void Buy()//成人买全票 { cout << "买全价票" << endl; } }; class Student : public Person { public: virtual void Buy()//学生买半票 { cout << "买半价票" << endl; } };
可以看到子类中定义了一个和父类完全相同的虚函数,构成了重写。
void func(Person& p) { p.Buy(); } void test() { Person p; Student s; //func(p); func(s); }
func函数的作用是,使用父类的指针调用重写的虚函数,当传p的时候,毫无疑问,调用了父类的虚函数,但是当传s的时候,调用的却是子类的虚函数,这是怎么回事呢?
这就是多态!
多态:当使用父类的指针/引用调用重写的虚函数时,当指向父类调用就是父类的虚函数,指向子类的调的就是子类的虚函数(和类型无关)。
可以说,构成了多态,就与对象有关,不构成多态,就和类型有关。这里需要说明三点:
1.去掉父类的virtual关键字,则不构成多态,也就是与类型有关。
2.去掉子类的virtual关键字,依然构成多态,我理解为编译器的默认。
3.当父类的返回值和子类的返回值不同(一个是void,一个是int)时,编译器报错:
重写虚函数返回类型有差异,且不是来自“Person::Buy”的协变
那么什么是协变呢,简单来说,就是虚函数的重写:函数名必须相同,参数必须相同,返回值可以不同(必须是父子关系的指针/引用):
这里的返回值构成了协变的条件,所以不会报错。
这里容易混淆重载,重定义和重写,总结一下:
注意:父类的析构函数最好定义为虚函数。
class A { public: A(int x = 1) :_pa(new int(x)) {} ~A() { cout << "~A()" << endl; } protected: int* _pa; }; class B : public A { public: B(int b) :A(b) ,_pb(new int(b)) { } ~B() { cout << "~B()" << endl; } protected: int* _pb; }; void test() { A* pa = new B(1); delete pa;//此时也应该调用B的析构 }
本来应该调用B的析构,但是没有,因为没有构成多态,与类型有关,若加上virtual,按上文所说,构成了虚函数得重写。
此时构成了多态:基类的指针调用重写的虚函数。
二:单继承,多继承
①单继承
class Base { public: virtual void func1() { cout << "Bass::func1()" << endl; } virtual void func2() { cout << "Bass::func2()" << endl; } void func3() { cout << "Bass::func3()" << endl; } public: int _a; };
可以看出,Base类中有两个虚函数和一个普通函数。
按照以前的知识,sizeof(Base)应该是4,因为函数放在公共区域,但是它的大小是8字节,怎么回事?来看
一看Base的布局情况:Base b1;
数组指针_vfptr与_a的总大小为8,这样就解释了原因,但是我觉得还需要深层探索:
查看内存:
对照内存,我得出了模型:
总结,当类中有序函数时,那么就会有一个_vfptr指针,是指向虚函数的表,它是一个指针数组,存的是虚函数的地址。
接下来介绍单继承模型:class Derive : public Bass { public: virtual void func1() { cout << "Derive::func1()" << endl; } virtual void func3() { cout << "Derive::func3()" << endl; } virtual void func4() { cout << "Derive::func4()" << endl; } public: int _b; };
Derive继承了Base类,并且重写了func1,而且有两个自己的虚函数。
查看布局:
但是这里只存了两个虚函数,剩下的func3和func3呢?
首先把_a,_b分别赋值为1,2,方便查看内存。
对比地址,看似已经得到了func3和func4的地址,但是有人告诉我说是假地址,那我可以打印地址来得到真地址:
typedef void(*U_FUNC)(); Print((int**)(*((int**)&b1))); Print((int**)(*((int**)&d1))); void Print(int** vfptr) { for (size_t i = 0; vfptr[i] != 0; ++i) { printf("vfper[%d]:%p->", i, vfptr[i]); U_FUNC f = (U_FUNC)vfptr[i]; f(); cout << endl; } cout << "____________________________________" << endl; }
这段代码其实很简单,取d的地址的前四个字节(64位下是前8个)传给Print函数,然后以4字节(64位下是8字节)为步数,打印指向的内容,结果如下:
注:地址和刚才不一样是因为清理了解决方案,这样得到了监视窗口显示不出来的func3和func4的真地址。
func3是普通函数,那么调用不同函数和调用虚函数有什么不同呢?
Bass* p = &b1; p->func1();//多态(父类指针调用重写虚函数,去对象的虚表里找)是动态联编 p->func3();//普通调用,静态联编
来从汇编角度看:
可以说,动态联编就是指针/引用+虚函数
多态就是动态联编+虚函数的重写
②多继承:
class Bass1 { public: virtual void func1() { cout << "Bass1::func1()" << endl; } virtual void func2() { cout << "Bass1::func2()" << endl; } private: int _a; }; class Bass2 { public: virtual void func1() { cout << "Bass2::func1()" << endl; } virtual void func2() { cout << "Bass2::func2()" << endl; } private: int _b; }; class Derive : public Bass1 , Bass2 { public: virtual void func1() { cout << "Derive::func1()" << endl; } virtual void func3() { cout << "Derive::func3()" << endl; } private: int _c; };
可以看到,这是一个多继承,Bass1和Bass2分别有两个虚函数func1和fun2,Derive继承了Bass1和Bass2,并且重写了func1,自己有一个虚函数func3。
接下来看Derive的布局:Derive d;
它有两个虚表,分别是两个父类的,存的是func1,func2,同样的,看不到func3,需要调用刚才写的打印函数:
首先打印Bass1的虚表:
Print((int**)(((int*)&d)));
可以看到,func3在Bass1的虚表里。
再来打印Bass2的虚表:
Print((int**)(((int*) ( (char*)&d+sizeof(Bass1) ) )));//第二个虚表需要往下移sizeof(Bass1)个字节。
这样,显而易见,func3存在了Bass1的虚表里,并且同时覆盖了两个父类的重写的虚函数。
多继承模型:
相关文章推荐
- c++多态对象模型:菱形继承和菱形虚拟继承
- C++中多态与对象模型及菱形继承
- C++对象模型---多态性的支持(单一继承下的多态)
- 第05章 CORE C++_对象的创建和使用_继承_多态_析构_xxx_cast_友元_只读成员_静态成员_多重继承_虚继承_内部类
- C++对象模型(二)多继承
- 虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数、C++对象模型图
- C++对象模型 多重继承与虚函数表
- C++ 虚继承的对象模型
- c++对象模型之虚继承
- 【C++】多态及其对象模型
- 从零开始学C++之虚继承和虚函数对C++对象内存模型造成的影响(类/对象的大小)
- c++的子对象,继承和多态
- C++虚继承内存对象模型探讨
- Microsoft Visual C++虚拟多继承 对象模型初步分析
- 深入探索C++对象模型之虚继承
- 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数、C++对象模型图
- c++对象模型(多态1)
- C++对象模型4--有重写的单继承
- C++对象布局及多态探索之菱形结构虚继承
- C++继承详解之三——菱形继承+虚继承内存对象模型详解vbptr(1)