C++语言支持多继承,一个子类能够有多个父类,子类拥有全部父类的成员变量,子类继承全部父类的成员函数,子类对象能够看成任意父类对象使用。ios
class Derived : public BaseA, public BaseB, public BaseC { };
经过多重继承获得的派生类对象可能具备不一样的地址。ide
#include <iostream> using namespace std; class BaseA { public: BaseA(int a) { ma = a; } private: int ma; }; class BaseB { public: BaseB(int b) { mb = b; } private: int mb; }; class Derived : public BaseA,public BaseB { public: Derived(int a, int b, int c):BaseA(a),BaseB(b) { mc = c; } private: int mc; }; struct Test { int a; int b; int c; }; int main(int argc, char *argv[]) { Derived d(1,2,3); cout << sizeof(d) << endl;//12 Test* p = (Test*)&d; cout << p->a << endl;//1 cout << p->b << endl;//2 cout << p->c << endl;//3 cout << &p->a << endl;//1 cout << &p->b << endl;//2 cout << &p->c << endl;//3 BaseA* pa = &d; BaseB* pb = &d; //子类对象的地址、首位继承类的成员地址 cout << &d << endl; cout << pa << endl; cout << &p->a <<endl; //子类对象的地址、次位继承类的成员地址 cout << pb << endl; cout << &p->b << endl; return 0; }
上述代码中,Derived类对象的内存布局以下:
Derived类对象从基类继承而来的处成员变量将根据继承的声明顺序进行依次排布。基于赋值兼容原则,若是BaseA类型指针pa、BaseB类型指针pb都指向子类对象d,pa将获得BaseA基类成员变量ma的地址,即子类对象的地址;pb将获得BaseB类成员变量mb的地址;所以,pa与pb的地址不相同。函数
上述类图中,Teacher类和Student类都会继承People的成员,Doctor会继承Teacher类和Student类的成员,所以Doctor将会有两份继承自顶层父类People的成员。布局
#include <iostream> #include <string> using namespace std; class People { public: People(string name, int age) { m_name = name; m_age = age; } void print() { cout << "name: " << m_name << " age: " << m_age <<endl; } private: string m_name; int m_age; }; class Teacher : public People { public: Teacher(string name, int age):People(name, age) { } }; class Student : public People { public: Student(string name, int age):People(name, age) { } }; class Doctor : public Teacher, public Student { public: Doctor(string name, int age): Teacher(name + "_1", age), Student(name + "_2", age) { } }; int main(int argc, char *argv[]) { Doctor doc("Bauer", 30); //doc.print();//error //error: request for member 'print' is ambiguous //Doctor继承了从Teacher,Student继承来的print函数。 //doc.People::print();//error //error: 'People' is an ambiguous base of 'Doctor' //People被继承了两次 doc.Teacher::print();//name:bauer_1 age:30 doc.Student::print();//name:bauer_2 age:30 return 0; }
在多继承中,保存共同基类的多份同名成员,能够在不一样的数据成员中分别存放不一样的数据,但保留多份数据成员的拷贝,不只占有较多的存储空间,增长了成员的冗余,还增长了访问的困难。C++提供了虚基类和虚继承机制,实现了在多继承中只保留一份共同成员。
C++对于菱形多继承致使的成员冗余问题的解决方案是使用虚继承。
虚继承中,中间层父类再也不关注顶层父类的初始化,最终子类必须直接调用顶层父类的构造函数。
虚继承的语法以下:class 派生类名:virtual 继承方式 基类名
学习
#include <iostream> #include <string> using namespace std; class People { public: People(string name, int age) { m_name = name; m_age = age; } void print() { cout << "name: " << m_name << " age: " << m_age <<endl; } private: string m_name; int m_age; }; class Teacher : virtual public People { public: Teacher(string name, int age):People(name, age) { } }; class Student : virtual public People { public: Student(string name, int age):People(name, age) { } }; class Doctor : public Teacher, public Student { public: //最终子类必须调用顶层父类的构造函数 Doctor(string name, int age): People(name, age), Teacher(name + "_1", age), Student(name + "_2", age) { } }; int main(int argc, char *argv[]) { Doctor doc("Bauer", 30); doc.print();//name:bauer age:30 doc.People::print();//name:bauer age:30 doc.Teacher::print();//name:bauer age:30 doc.Student::print();//name:bauer age:30 return 0; }
上述代码中,使用虚继承解决了成员冗余的问题。
虚继承解决了多继承产生的数据冗余问题,可是中间层父类再也不关心顶层父类的初始化,最终子类必须直接调用顶层父类的构造函数。this
上述类图中,Derived类继承自BaseA和BaseB类,funcA和funcB为虚函数,Derived对象模型以下:spa
#include <iostream> #include <string> using namespace std; class BaseA { public: BaseA(int a) { m_a = a; } virtual void funcA() { cout << "BaseA::funcA()" <<endl; } private: int m_a; }; class BaseB { public: BaseB(int b) { m_b = b; } virtual void funcB() { cout << "BaseB::funcB()" <<endl; } private: int m_b; }; class Derived : public BaseA, public BaseB { public: Derived(int a, int b, int c):BaseA(a),BaseB(b) { m_c = c; } private: int m_c; }; struct Test { void* vptrA; int a; void* vptrB; int b; int c; }; int main(int argc, char *argv[]) { cout << sizeof(Derived) << endl; Derived d(1,2,3); Test* pTest = (Test*)&d; cout << pTest->a <<endl;//1 cout << pTest->b <<endl;//2 cout << pTest->c <<endl;//3 cout << pTest->vptrA <<endl;// cout << pTest->vptrB <<endl;// return 0; }
菱形继承示例代码以下:设计
#include <iostream> #include <string> using namespace std; class People { public: People(string name, int age) { m_name = name; m_age = age; } void print() { cout << "name: " << m_name << " age: " << m_age <<endl; } private: string m_name; int m_age; }; class Teacher : public People { string m_research; public: Teacher(string name, int age, string research):People(name + "_1", age + 1) { m_research = research; } }; class Student : public People { string m_major; public: Student(string name, int age,string major):People(name + "_2", age + 2) { m_major = major; } }; class Doctor : public Teacher, public Student { string m_subject; public: Doctor(string name, int age,string research, string major, string subject): Teacher(name, age,research),Student(name, age, major) { m_subject = subject; } }; struct Test { string name1; int age1; string research; string name2; int age2; string major; string subject; }; int main(int argc, char *argv[]) { Doctor doc("Bauer", 30, "Computer", "Computer Engneering", "HPC"); cout << "Doctor size: " << sizeof(doc) << endl; Test* pTest = (Test*)&doc; cout << pTest->name1 << endl; cout << pTest->age1 << endl; cout << pTest->research << endl; cout << pTest->name2 << endl; cout << pTest->age2 << endl; cout << pTest->major << endl; cout << pTest->subject << endl; return 0; } // output: // Doctor size: 28 // Bauer_1 // 31 // Computer // Bauer_2 // 32 // Computer Engneering // HPC
上述代码中,底层子类对象的内存局部以下:
底层子类对象中,分别继承了中间层父类从顶层父类继承而来的成员变量,所以内存模型中含有两份底层父类的成员变量。
若是顶层父类含有虚函数,中间层父类会分别继承顶层父类的虚函数表指针,所以,底层子类对象内存布局以下:指针
#include <iostream> #include <string> using namespace std; class People { public: People(string name, int age) { m_name = name; m_age = age; } virtual void print() { cout << "name: " << m_name << " age: " << m_age <<endl; } private: string m_name; int m_age; }; class Teacher : public People { string m_research; public: Teacher(string name, int age, string research):People(name + "_1", age + 1) { m_research = research; } }; class Student : public People { string m_major; public: Student(string name, int age,string major):People(name + "_2", age + 2) { m_major = major; } }; class Doctor : public Teacher, public Student { string m_subject; public: Doctor(string name, int age,string research, string major, string subject): Teacher(name, age,research),Student(name, age, major) { m_subject = subject; } virtual void print() { } }; struct Test { void* vptr1; string name1; int age1; string research; void* vptr2; string name2; int age2; string major; string subject; }; int main(int argc, char *argv[]) { Doctor doc("Bauer", 30, "Computer", "Computer Engneering", "HPC"); cout << "Doctor size: " << sizeof(doc) << endl; Test* pTest = (Test*)&doc; cout << pTest->vptr1 << endl; cout << pTest->name1 << endl; cout << pTest->age1 << endl; cout << pTest->research << endl; cout << pTest->vptr2 << endl; cout << pTest->name2 << endl; cout << pTest->age2 << endl; cout << pTest->major << endl; cout << pTest->subject << endl; return 0; } // output: // Doctor size: 28 // 0x405370 // Bauer_1 // 31 // Computer // 0x40537c // Bauer_2 // 32 // Computer Engneering // HPC
虚继承是解决C++多重继承问题的一种手段,虚继承的底层实现原理与C++编译器相关,通常经过虚基类指针和虚基类表实现,每一个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4(8)字节)和虚基类表(不占用类对象的存储空间)(虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份);当虚继承的子类被当作父类继承时,虚基类指针也会被继承。
在虚继承状况下,底层子类对象的布局不一样于普通继承,须要多出一个指向中间层父类对象的虚基类表指针vbptr。
vbptr是虚基类表指针(virtual base table pointer),vbptr指针指向一个虚基类表(virtual table),虚基类表存储了虚基类相对直接继承类的偏移地址;经过偏移地址能够找到虚基类成员,虚继承不用像普通多继承维持着公共基类(虚基类)的两份一样的拷贝,节省了存储空间。code
#include <iostream> #include <string> using namespace std; class People { public: People(string name, int age) { m_name = name; m_age = age; } void print() { cout << "this: " << this <<endl; } private: string m_name; int m_age; }; class Teacher : virtual public People { string m_research; public: Teacher(string name, int age, string research):People(name + "_1", age + 1) { m_research = research; } void print() { cout << "this: " << this <<endl; } }; class Student : virtual public People { string m_major; public: Student(string name, int age,string major):People(name + "_2", age + 2) { m_major = major; } void print() { cout << "this: " << this <<endl; } }; class Doctor : public Teacher, public Student { string m_subject; public: Doctor(string name, int age,string research, string major, string subject): People(name, age),Teacher(name, age,research),Student(name, age, major) { m_subject = subject; } }; struct Test { void* vbptr_left; string research; void* vbptr_right; string major; string subject; string name; int age; }; int main(int argc, char *argv[]) { Doctor doc("Bauer", 30, "Computer", "Computer Engneering", "HPC"); cout << "Doctor size: " << sizeof(doc) << endl; Test* pTest = (Test*)&doc; cout << pTest->vbptr_left << endl; cout << *(int*)pTest->vbptr_left << endl; cout << pTest->research << endl; cout << pTest->vbptr_right << endl; cout << *(int*)pTest->vbptr_right << endl; cout << pTest->major << endl; cout << pTest->subject << endl; cout << pTest->name << endl; cout << pTest->age << endl; return 0; } // output: // Doctor size: 28 // 0x40539c // 12 // Computer // 0x4053a8 // 0 // Computer Engneering // HPC // Bauer // 30
上述代码没有虚函数,在G++编译器打印结果如上,底层子类对象的内存布局以下:
#include <iostream> #include <string> using namespace std; class People { public: People(string name, int age) { m_name = name; m_age = age; } virtual void print() { cout << "this: " << this <<endl; } private: string m_name; int m_age; }; class Teacher : virtual public People { string m_research; public: Teacher(string name, int age, string research):People(name + "_1", age + 1) { m_research = research; } void print() { cout << "this: " << this <<endl; } virtual void func1() {} }; class Student : virtual public People { string m_major; public: Student(string name, int age,string major):People(name + "_2", age + 2) { m_major = major; } void print() { cout << "this: " << this <<endl; } virtual void func2() {} }; class Doctor : public Teacher, public Student { string m_subject; public: Doctor(string name, int age,string research, string major, string subject): People(name, age),Teacher(name, age,research),Student(name, age, major) { m_subject = subject; } void print() { cout << "this: " << this <<endl; } virtual void func3() {} }; struct Test { void* vbptr_left; char* research; void* vbptr_right; char* major; char* subject; void* vptr_base; char* name; long age; }; int main(int argc, char *argv[]) { Doctor doc("Bauer", 30, "Computer", "Computer Engneering", "HPC"); cout << "Doctor size: " << sizeof(doc) << endl; Test* pTest = (Test*)&doc; cout << pTest->vbptr_left << endl; cout << std::hex << *(int*)pTest->vbptr_left << endl; cout << std::dec << *((int*)pTest->vbptr_left+8) << endl; cout << std::dec << *((int*)pTest->vbptr_left+16) << endl; cout << std::dec << *((int*)pTest->vbptr_left+24) << endl; cout << pTest->research << endl; cout << pTest->vbptr_right << endl; cout << pTest->major << endl; cout << pTest->subject << endl; cout << pTest->vptr_base << endl; cout << pTest->name << endl; cout << pTest->age << endl; return 0; }
上述代码中,使用了虚继承,所以不一样的C++编译器实现原理不一样。
对于GCC编译器,People对象大小为char + int + 虚函数表指针,Teacher对象大小为char+虚基类表指针+A类型的大小,Student对象大小为char+虚基类表指针+A类型的大小,Doctor对象大小为char + int +虚函数表指针+char+虚基类表指针+char+虚基类表指针+char*。中间层父类共享顶层父类的虚函数表指针,没有本身的虚函数表指针,虚基类指针不共享,所以都有本身独立的虚基类表指针。
VC++、GCC和Clang编译器的实现中,不论是否是虚继承仍是有虚函数,其虚基类指针都不共享,都是单独的。对于虚函数表指针,VC++编译器根据是否为虚继承来判断是否在继承关系中共享虚表指针。若是子类是虚继承拥有虚函数父类,且子类有新加的虚函数时,子类中则会新加一个虚函数表指针;GCC编译器和Clang编译器的虚函数表指针在整个继承关系中共享的。
G++编译器对于类的内存分布和虚函数表信息命令以下:
g++ -fdump-class-hierarchy main.cpp cat main.cpp.002t.class
VC++编译器对于类的内存分布和虚函数表信息命令以下:cl main.cpp /d1reportSingleClassLayoutX
Clang编译器对于类的内存分布和虚函数表信息命令以下:clang -Xclang -fdump-record-layouts
全部的虚函数都保存在虚函数表中,多重继承可能产生多个虚函数表。多继承中,当子类对父类的虚函数重写时,子类的函数覆盖父类的函数在对应虚函数表中的虚函数位置;当子类有新的虚函数时,新的虚函数被加到第一个基类的虚函数表的末尾。当dynamic_cast对子类对象进行转换时,子类和第一个基类的地址相同,不须要移动指针,但当dynamic_cast转换子类到其余父类时,须要作相应的指针调整。
C++语言中,一般对指针进行类型转换,不会改变指针的值,只会改变指针的类型(即改变编译器对该指针指向内存的解释方式),但在C++多重继承中并不成立。
#include <iostream> using namespace std; class BaseA { public: BaseA(int value = 0) { data = value; } virtual void printA() { cout << "BaseA::print data = " << data << endl; } protected: int data; }; class BaseB { public: BaseB(int value = 0) { data = value; } virtual void printB() { cout << "BaseB::print data = " << data << endl; } protected: int data; }; class Derived : public BaseA, public BaseB { public: Derived(int value = 0) { data = value; } virtual void printA() { cout << "Derived printA data = " << data << endl; } virtual void printB() { cout << "Derived printB data = " << data << endl; } protected: int data; }; int main(int argc, char *argv[]) { Derived* dpd = new Derived(102); cout << dpd << endl;//0x8d1190 BaseA* bpa = (BaseA*)dpd; cout << bpa << endl;//0x8d1190 BaseB* bpb = (BaseB*)dpd; cout << bpb << endl;//0x8d1198 cout << (dpd == bpb) << endl;//1 return 0; }
上述代码中,指向Derived对象的指针转换为基类BaseA和BaseB后,指针值并不相同。dpd指针、bpa指针与bpb指针相差8个字节的地址空间,即BaseA类虚函数表指针与data成员占用的空间。
将一个派生类的指针转换成某一个基类指针,C++编译器会将指针的值偏移到该基类在对象内存中的起始位置。
cout << (dpd == bpb) << endl;//1
上述代码打印出1,C++编译器屏蔽了指针的差别,当C++编译器遇到一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针作隐式类型提高以屏蔽多重继承带来的指针差别。
派生类对象指针转换为不一样基类对象指针时,C++编译器会按照派生类声明的继承顺序,转换为第一基类时指针不变,之后依次向后偏移前一基类所占字节数。
多继承下,指针类型转换须要考虑this指针调整的问题。
多继承中,若是中间层父类有两个以上父类实现了虚函数,会形成子类产生多个虚函数表指针,可使用dynamic_cast关键字做类型转换。
工程实践中一般使用单继承某个类和实现多个接口解决多继承的问题。
代码实例:
#include <iostream> #include <string> using namespace std; class Base { protected: int mi; public: Base(int i) { mi = i; } int getI() { return mi; } bool equal(Base* obj) { return (this == obj); } }; class Interface1 { public: virtual void add(int i) = 0; virtual void minus(int i) = 0; }; class Interface2 { public: virtual void multiply(int i) = 0; virtual void divide(int i) = 0; }; class Derived : public Base, public Interface1, public Interface2 { public: Derived(int i) : Base(i) { } void add(int i) { mi += i; } void minus(int i) { mi -= i; } void multiply(int i) { mi *= i; } void divide(int i) { if( i != 0 ) { mi /= i; } } }; int main() { Derived d(100); Derived* p = &d; Interface1* pInt1 = &d; Interface2* pInt2 = &d; cout << "p->getI() = " << p->getI() << endl; // 100 pInt1->add(10); pInt2->divide(11); pInt1->minus(5); pInt2->multiply(8); cout << "p->getI() = " << p->getI() << endl; // 40 cout << endl; cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl; cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl; return 0; }
在程序设计中最好不要出现多继承,要有也是继承多个做为接口使用抽象类(只声明须要的功能,没有具体的实现)。由于出现通常的多继承自己就是一种很差的面向对象程序设计。