C++对象模型:单继承,多继承,虚继承
https://www.cnblogs.com/raichen/p/5744300.html
相关解释非常清晰,关于最后菱形虚继承可以参考此文
https://www.cnblogs.com/QG-whz/p/4909359.html
------待补充、有空会提炼精简相关内容------
一、类成员分类
c++的对象模型:(这也是正在使用的)
首先,非静态的数据成员放置在每一个类对象中,static数据成员放在类对象外。而函数,不管是静态还是非静态,都是在类外的,对于虚函数,通过虚函数表加上虚指针来支持。
- 每个类生成一个虚表(virtual table,vtbl),虚表中存放一堆指针,指向该类的每一个虚函数,地址将按照声明顺序排列
- 每一个类对象,都拥有一个虚表指针(vptr),由编译器生成,虚表指针的设定和重置,都有类的复制控制(也就是构造,析构,赋值操作符)完成。现在许多编译器,把vptr放在一个类对象的最前端。
二、单继承(父类含虚函数)
原则:
- 子类和父类,都各自拥有一个虚函数表。
- 子类如果没有重写(override)父类的虚函数,则,直接用父类的虚函数。
- 如果子类重写了父类虚函数,则子类虚函数会覆盖虚表中对应的父类虚函数
- 如果子类声明了自己新的虚函数,该虚函数地址会扩充到虚函数表最后。
#include <iostream> using namespace std; class Base { public: virtual void fun1(){ cout << "Base fun1" << endl; } virtual void fun2(){ cout << "Base fun2" << endl; } private: int a; }; class Derive : public Base { public: void fun2(){ cout << "Derive fun2" << endl; } virtual void fun3(){} private: int b; }; int main() { Base b; Derive d; Base *p = &d; p->fun1(); p->fun2(); system("pause"); return 0; }
三、多继承
(1)一般多继承(非菱形继承)
- 如果子类新增虚函数,放在声明的第一个父类虚函数表中。
- 如果子类重写了父类的虚函数,所有父类的虚函数表都会改变。
- 内存布局中,父类按照声明顺序排列。
#include <iostream> using namespace std; class Base1 { public: virtual void fun1(){} private: int m_base1; }; class Base2 { public: virtual void fun1(){} virtual void fun2(){} private: int m_base2; }; class Derive : public Base1,public Base2 { public: void fun1(){} virtual void fun3(){} private: int m_derive; }; int main() { Base1 b1; Base2 b2; Derive d; cout <<"b1:" <<sizeof(b1) << endl; cout << "b2:" << sizeof(b2) << endl; cout <<"d:" << sizeof(d) << endl; system("pause"); return 0; }
(2)菱形继承
也叫重复继承,基类被某个派生类简单重复继承了多次。在派生类中拥有多份基类实例。
class B { public: int ib; public: B(int i=1) :ib(i){} virtual void f() { cout << "B::f()" << endl; } virtual void Bf() { cout << "B::Bf()" << endl; } }; class B1 : public B { public: int ib1; public: B1(int i = 100 ) :ib1(i) {} virtual void f() { cout << "B1::f()" << endl; } virtual void f1() { cout << "B1::f1()" << endl; } virtual void Bf1() { cout << "B1::Bf1()" << endl; } }; class B2 : public B { public: int ib2; public: B2(int i = 1000) :ib2(i) {} virtual void f() { cout << "B2::f()" << endl; } virtual void f2() { cout << "B2::f2()" << endl; } virtual void Bf2() { cout << "B2::Bf2()" << endl; } }; class D : public B1, public B2 { public: int id; public: D(int i= 10000) :id(i){} virtual void f() { cout << "D::f()" << endl; } virtual void f1() { cout << "D::f1()" << endl; } virtual void f2() { cout << "D::f2()" << endl; } virtual void Df() { cout << "D::Df()" << endl; } };
我们可以看到在D类中,有俩个B类的数据成员,一个来自B1,一个来自B2,会引起程序歧义。
调用看看。
三、虚继承
虚继承可以解决菱形继承中拥有多个间接父类实例的情况。
(1)简单虚继承
- 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生存一个新的函数指针(vptr)以及一张新的虚函数表。这个vptr位于对象内存最前面。
- 虚继承的子类中,单独保留了父类的vptr与虚函数表
- 虚继承的子类中,还有虚基类表指针(vbptr)
当虚继承时,子类会生成一个隐藏的虚基类指针(当然也占用内存) vbptr,这个虚基类表指针总在虚函数指针后。
因而,对某个类实例来说,如果它有虚基类指针,那么虚基类指针可能在实例的0字节偏移处(该类没有vptr时,vbptr就处于类实例内存布局的最前面,否则vptr处于类实例内存布局的最前面),也可能在类实例的4字节偏移处。
int main() { B1 a; cout <<"B1对象内存大小为:"<< sizeof(a) << endl; //取得B1的虚函数表 cout << "[0]B1::vptr"; cout << "\t地址:" << (int *)(&a)<< endl; //输出虚表B1::vptr中的函数 for (int i = 0; i<2;++ i) { cout << " [" << i << "]"; Fun fun1 = (Fun)*((int *)*(int *)(&a) + i); fun1(); cout << "\t地址:\t" << *((int *)*(int *)(&a) + i) << endl; } //[1] cout << "[1]vbptr " ; cout<<"\t地址:" << (int *)(&a) + 1<<endl; //虚表指针的地址 //输出虚基类指针条目所指的内容 for (int i = 0; i < 2; i++) { cout << " [" << i << "]"; cout << *(int *)((int *)*((int *)(&a) + 1) + i); cout << endl; } //[2] cout << "[2]B1::ib1=" << *(int*)((int *)(&a) + 2); cout << "\t地址:" << (int *)(&a) + 2; cout << endl; //[3] cout << "[3]值=" << *(int*)((int *)(&a) + 3); cout << "\t\t地址:" << (int *)(&a) + 3; cout << endl; //[4] cout << "[4]B::vptr"; cout << "\t地址:" << (int *)(&a) +3<< endl; //输出B::vptr中的虚函数 for (int i = 0; i<2; ++i) { cout << " [" << i << "]"; Fun fun1 = (Fun)*((int *)*((int *)(&a) + 4) + i); fun1(); cout << "\t地址:\t" << *((int *)*((int *)(&a) + 4) + i) << endl; } //[5] cout << "[5]B::ib=" << *(int*)((int *)(&a) + 5); cout << "\t地址: " << (int *)(&a) + 5; cout << endl;
(2)虚拟菱形继承(菱形虚继承)
- 在D类对象内存中,基类出现的顺序是:先是B1(最左父类),然后是B2(次左父类),最后是B(虚祖父类)
- D类对象的数据成员id放在B类前面,两部分数据依旧以0来分隔。
- 编译器没有为D类生成一个它自己的vptr,而是覆盖并扩展了最左父类的虚基类表,与简单继承的对象模型相同。
- 超类B的内容放到了D类对象内存布局的最后。
int main() { D d; cout << "D对象内存大小为:" << sizeof(d) << endl; //取得B1的虚函数表 cout << "[0]B1::vptr"; cout << "\t地址:" << (int *)(&d) << endl; //输出虚表B1::vptr中的函数 for (int i = 0; i<3; ++i) { cout << " [" << i << "]"; Fun fun1 = (Fun)*((int *)*(int *)(&d) + i); fun1(); cout << "\t地址:\t" << *((int *)*(int *)(&d) + i) << endl; } //[1] cout << "[1]B1::vbptr "; cout << "\t地址:" << (int *)(&d) + 1 << endl; //虚表指针的地址 //输出虚基类指针条目所指的内容 for (int i = 0; i < 2; i++) { cout << " [" << i << "]"; cout << *(int *)((int *)*((int *)(&d) + 1) + i); cout << endl; } //[2] cout << "[2]B1::ib1=" << *(int*)((int *)(&d) + 2); cout << "\t地址:" << (int *)(&d) + 2; cout << endl; //[3] cout << "[3]B2::vptr"; cout << "\t地址:" << (int *)(&d) + 3 << endl; //输出B2::vptr中的虚函数 for (int i = 0; i<2; ++i) { cout << " [" << i << "]"; Fun fun1 = (Fun)*((int *)*((int *)(&d) + 3) + i); fun1(); cout << "\t地址:\t" << *((int *)*((int *)(&d) + 3) + i) << endl; } //[4] cout << "[4]B2::vbptr "; cout << "\t地址:" << (int *)(&d) + 4 << endl; //虚表指针的地址 //输出虚基类指针条目所指的内容 for (int i = 0; i < 2; i++) { cout << " [" << i << "]"; cout << *(int *)((int *)*((int *)(&d) + 4) + i); cout << endl; } //[5] cout << "[5]B2::ib2=" << *(int*)((int *)(&d) + 5); cout << "\t地址: " << (int *)(&d) + 5; cout << endl; //[6] cout << "[6]D::id=" << *(int*)((int *)(&d) + 6); cout << "\t地址: " << (int *)(&d) + 6; cout << endl; //[7] cout << "[7]值=" << *(int*)((int *)(&d) + 7); cout << "\t\t地址:" << (int *)(&d) + 7; cout << endl; //间接父类 //[8] cout << "[8]B::vptr"; cout << "\t地址:" << (int *)(&d) + 8 << endl; //输出B::vptr中的虚函数 for (int i = 0; i<2; ++i) { cout << " [" << i << "]"; Fun fun1 = (Fun)*((int *)*((int *)(&d) + 8) + i); fun1(); cout << "\t地址:\t" << *((int *)*((int *)(&d) + 8) + i) << endl; } //[9] cout << "[9]B::id=" << *(int*)((int *)(&d) + 9); cout << "\t地址: " << (int *)(&d) +9; cout << endl; getchar(); }