虚继承和虚基类
为了解决继承中的二义性问题,提出的虚基类和虚继承。虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> using namespace std;class A {public : A () { cout << "A()" << endl; } ~A () { cout << "~A()" << endl; } }; class B : public A {public : B () { cout << "B()" << endl; } ~B () { cout << "~B()" << endl; } }; class C : public A {public : C () { cout << "C()" << endl; } ~C () { cout << "~C()" << endl; } }; class D : public B, public C {public : D () { cout << "D()" << endl; } ~D () { cout << "~D()" << endl; } }; int main () { D d; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 @└────> # ./a.out A() B() A() C() D() ~D() ~C() ~A() ~B() ~A()
可以看到,在以上存在多继承且为菱形继承的情况下,构造函数的调用顺序是 ABACD,这表明继承 B 和 C 的时候夹杂了两份 A 的内容。这样也有一个问题,就是在试图用基类指针指向 D 对象时,编译器不知道应该让它指向哪个 A 的内存,因为在 d 中有两个 A 的实例。虚继承就是为了解决这个问题的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> using namespace std;class A {public : A () { cout << "A()" << endl; } ~A () { cout << "~A()" << endl; } }; class B : virtual public A { public : B () { cout << "B()" << endl; } ~B () { cout << "~B()" << endl; } }; class C : virtual public A { public : C () { cout << "C()" << endl; } ~C () { cout << "~C()" << endl; } }; class D : public B, public C {public : D () { cout << "D()" << endl; } ~D () { cout << "~D()" << endl; } }; int main () { D d; A* a = &d; return 0 ; }
1 2 3 4 5 6 7 8 9 @└────> # ./a.out A() B() C() D() ~D() ~C() ~B() ~A()
这样 D 的内存中就只有一份 A 了,并且用基类指针指向该地址也能正确进行。虚基类并不是在声明基类时声明的 ,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。
纯虚函数
cpp 支持编译时多态和运行时多态,函数重载就是编译时多态,而派生类和虚函数则为运行时多态。
编译时多态和运行时多态的区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译阶段 就可以确定函数的调用地址,并产生代码,就是编译时多态,就是说地址是早绑定的。而如果函数的调用地址不能在编译期间确定,而需要在运行时才能决定,这这就属于运行时多态。 向上类型转换问题,对象可以作为自己的类或者作为它的基类的对象来使用。还能通过基类的地址来操作它。取一个对象的地址(指针或引用),并将其作为基类的地址来处理,这种称为向上类型转换。也就是说:父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象。
Cpp 动态多态性是通过虚函数来实现的,虚函数允许派生类重新定义基类成员函数,而派生类重新定义基类虚函数的做法称为覆盖(override),或者称为重写。这部分原理可见另一篇文章。 cpp虚函数表
在设计时,常常希望基类仅仅作为其派生类的一个接口。这就是说,仅想对基类进行向上类型转换,使用它的接口,而不希望用户实际的创建一个基类的对象。同时创建一个纯虚函数允许接口中放置成员原函数,而不一定要提供一段可能对这个函数毫无意义的代码。做到这点,可以在基类中加入至少一个纯虚函数(pure virtual function),使得基类称为抽象类(abstract class)。
纯虚函数使用关键字 virtual,并在其后面加上 = 0。如果试图去实例化一个抽象类,编译器则会阻止这种操作。当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类 。virtual void fun() = 0,告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;class A {public : A () { cout << "A()" << endl; } virtual void func () = 0 ; virtual ~A () { cout << "~A()" << endl; } }; class B : public A {public : B () { cout << "B()" << endl; } void func () { cout << "B::func()" << endl; } ~B () { cout << "~B()" << endl; } }; int main () { B b; A* a = &b; a->func (); return 0 ; }
1 2 3 4 5 6 @└────> # ./a.out A() B() B::func() ~B() ~A()