代码改变世界

虚基类的用法

2011-03-15 15:14  bingcaihuang  阅读(991)  评论(0编辑  收藏  举报

由类A,类B1和类B2以及类C组成了类继承的层次结构。在该结构中,类C的对象将包含两个类A的子对象。由于类A是派生类C两条继承路径上的一个公共基类,那么这个公共基类将在派生类的对象中产生多个基类子对象。如果要想使这个公共基类在派生类中只产生一个基类子对象,则必须将这个基类设定为虚基类。

虚基类的引入和说明

     前面简单地介绍了要引进虚基类的原因。实际上,引进虚基类的真正目的是为了解决二义性问题。

     虚基类说明格式如下:

     virtual <继承方式><基类名>

     其中,virtual是虚类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。例如:

     class A
     {
     public:
       void f();
     protected:
       int a;
     };
     class B : virtual public A
     {
     protected:
         int b;
     };
     class C : virtual public A
     {
     protected:
         int c:
     };
     class D : public B, public C
     {
     public:
         int g();
     private:
         int d;
     };

     由于使用了虚基类,使得类A,类B,类C和类D之间关系用DAG图示法表示如下:

                        A{ f(), a }
                         /       \
                       B{b}     C{c}
                         \       /
                         D{g(),d}

     从该图中可见不同继承路径的虚基类子对象被合并成为一个对象。这便是虚基类的作用,这样将消除了合并之前可能出现的二义性。这时,在类D的对象中只存在一个类A的对象。因此,下面的引用都是正确的:

     D n;
     n.f();    //对f()引用是正确的。
     void D::g()
     {
     f();    //对f()引用是正确的。
     }

     下面程序段是正确的。

     D n;
     A *pa;
     pa = &n;

     其中,pa是指向类A对象的指针,n是类D的一个对象,&n是n对象的地址。pa=&n是让pa指针指向类D的对象,这是正确的,并且也无二义性。

虚基类的构造函数

     前面讲过,为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最派生类。C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。

     从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数 的调用。但是,只有用于建立对象的那个最派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的对象只初始化一次。

     C++又规定,在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。

     下面举一例子说明具有虚基类的派生类的构造函数的用法。

     #include <iostream.h>
     class A
     {
     public:
     A(const char *s) { cout<<s<<endl; }
     ~A() {}
     };

     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, public 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;
     }
     };

     void main()
     {
     D *ptr = new D("class A", "class B", "class C", "class D");
     delete ptr;
     }

     该程序的输出结果为:

     class A
     class B
     class C
     class D

     在派生类B和C中使用了虚基类,使得建立的D类对象只有一个虚基类子对象。

     在派生类B,C,D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数。

     在建立类D对象时,只有类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,并且仅调用一次,而类D基类的构造函数的成员初始化列表中列出的虚基类构造函数不被执行。这一点将从该程序的输出结果可以看出。