C++虚基类构造函数详解(调用顺序)之一
- 解释某个函数,我通常的讲解不会先去长篇大论去空谈,先整个例子来看看!!走起....
#include <iostream> #include <string> using namespace std; class A { public: A(const char*s) { cout<<s<<endl; } }; class B:virtual public A { public: B(const char*s1,const char*s2):A(s1) { cout <<s2<<endl; } }; class C:virtual public A { public: C(const char*s1,const char*s2):A(s1) { cout<<s2<<endl; } }; class D:public B,C { public: D(const char *s1,const char *s2,const char*s3,const char*s4):B(s1,s2),C(s1,s3),A(s1) { cout <<s4<<endl; } }; int main(int argc, char* argv[]) { D *ptr = new D("class A","class B","class C","class D"); delete ptr; ptr = NULL; return 0; }
先不要忙着去执行代码!!
来看几个基本概念:
一、虚基类的作用:
当一个类的部分或者全部基类来自另一个共同的基类时,这些直接基类中从上一级共同基类继承来的 就拥有相同的名称。在派生类的对象中,这些同名数据成员在内存中同时拥有多个拷贝,同一个函数名会有多个映射。我们可以使用作用域分蝙蝠来唯一标识并分别访问他们,也可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只用一个拷贝,同一个函数名也只有一个映射。
二、虚基类的声明 语法形式:
class 派生类名:virtual 继承方式 基类名
三、使用虚基类时应该注意:
1>一个类可以在一个类族中用作虚基类,也可以用作非虚基类。
2>在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的对象。
3>虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化。
4>最派生类是指在继承类结构中建立对象时所指定的类。
5>在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数。
6>在虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中,都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
7>在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,基类的构造函数先于非虚基类的构造函数执行。
8>虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。
温馨提示:使用多重继承时要十分小心,经常会出现二义性。许多专业人员认为:不要提倡在程序中使用多重继承,只有在比较简单和不易出现二义性的情况或是在必要时才使用多重继承,能用单一继承解决的问题就不要使用多重继承,也是由于这个原因,有些面向对象的程序设计语言,并不支持多重继承。
现在对虚基类构造函数了解了没??如果还不了解那么咱们就继续深入研究.....
首先,要知道虚拟继承与普通继承的区别:
假设derived继承自base类,那么derived与base是一种“is a”的关系,即derived类是base类,而反之错误;
假设derived虚继承自base类,那么derived与base是一种“has a”的关系,即derived类有一个指向base类的vptr。
因此虚继承可以认为不是一种继承关系,而可以认为是一种组合的关系。因为虚继承有着“继承”两个关键字,那么大部分人都认为虚继承与普通继承的用法没有什么太大的不同,由此用在继承体系中,这种将虚继承认为是普通继承的危害更加大!先用一个例子来说明问题:
#include <iostream> using namespace std; class base { public: base() { cout <<"base::base()!"<<endl; } void printBase() { cout<<"base::printBase()!"<<endl; } }; class derived:public base { public: derived() { cout<<"derived::derived()!"<<endl; } void printDerived() { cout<<"derived::printDerived()!"<<endl; } }; int main(int argc, char* argv[]) { derived oo; base oo1(static_cast<base>(oo)); oo1.printBase(); cout <<"---------------------"<<endl; derived oo2= static_cast<derived&>(oo1); oo2.printDerived(); }
运行结果:
对前面的例子稍加修改......................
#include <iostream> using namespace std; class base1 { public: base1() { cout<<"base::base()!"<<endl; } void printBase() { cout<<"base::printBase()!"<<endl; } }; class derived1:virtual public base1 { public: derived1() { cout<<"derived::derived()!"<<endl; } void printDerived() { cout <<"derived::printDerived()!"<<endl ; } }; int main(int argc, char* argv[]) { derived1 oo; base1 oo1(static_cast<base1>(oo)); oo1.printBase(); derived1 oo2 = static_cast<derived1&>(oo1); oo2.printDerived(); return 0; }
会发现编译错误:error C2635: cannot convert a 'base1*' to a 'derived1*'; conversion from a virtual base class is implied(代码中红色部分出错)
可以看到不能将基类通过static_cast转换为继承类。我们知道c++提供的强制转换函数static_cast对于继承体系中的类对象的转换一般是可行的。那么这里为什么不可以呢??
virtual base class的原始模型是在class object中为每一个有关联的virtual base class加上一个指针vptr,该指针指向virtual基类表。有的编译器是在继承类已存在的virtual table直接扩充导入一个virtual base class table。不管怎么样由于虚继承已完全破坏了继承体系,不能按照平常的继承体系来进行类型转换。
- 我们清楚了虚基类构造函数是怎么回事,那么接下来讲解一下虚基类构造函数调用顺序!!
我们下来了解虚拟继承中遇到最广泛的菱形结构:
#include <iostream> using namespace std; class stream { public: stream() { cout <<"stream::stream()!"<<endl; } }; class iistream:virtual stream { public: iistream() { cout <<"istream::istream()!"<<endl; } }; class oostream:virtual stream { public: oostream() { cout <<"ostream::ostream()!"<<endl; } }; class iiostream:public iistream,oostream { public: iiostream() { cout<<"iiostream::iiostream()!"<<endl; } }; int main(int argc, char* argv[]) { iiostream oo; return 0; }
运行结果:
本来虚拟继承的目的就是当多重继承出现重复的基类时,其只保存一份基类,减少内存开销。
这样子的菱形结构,使公共基类只产生一个拷贝。
从基类stream派生新类时,使用virtual将类stream说明为虚基类,这时派生类istream、ostream包含一个指向虚基类的vptr,而不会产生实际的stream空间。所以最终iiostream也含有一个指向虚基类的vptr,调用stream中的成员方法时,通过vptr去调用,不会产生二义性!
现在我们换种方式使用虚继承:
#include <iostream> using namespace std; class stream { public: stream() { cout <<"stream::stream()!"<<endl; } }; class iistream:public stream { public: iistream() { cout <<"istream::istream()!"<<endl; } }; class oostream:public stream { public: oostream() { cout <<"ostream::ostream()!"<<endl; } }; class iiostream:virtual iistream,oostream { public: iiostream() { cout<<"iiostream::iiostream()!"<<endl; } }; int main(int argc, char* argv[]) { iiostream oo; return 0; }
运行结果:
从结果上可以看到,其构造过程中重复出现基类的stream的构造过程,这样就完全没有达到虚拟继承的目的。其继承结构为:
从继承结构可以看出,如果iiostream对象调用基类stream重的成员方法,会导致方法的二义性。因为iiostream含有指向其虚继承基类istream,ostream的vptr。而istream,ostream包含了stream的空间,所以导致iiostream不知道导致时调用那个stream的方法。要解决该问题,即在调用成员方法时需要加上作用域!
后续