C++_虚基类

虚基类

1.为什么要引入虚基类

当某一个类的多个直接基类是从另一个共同基类派生而来时,这些直接基类中从上一级基类继承来的成员就拥有相同的名称。在派生类的对象中,这些同名成员在内存中同时拥有多个拷贝。

如何进行分辨呢?

  • 一种方法就是使用作用域标示符来唯一表示它们
  • 另一种方法是定义虚基类,使派生类中只保留一份拷贝。
例 虚基类的引例
#include <iostream.h>
class base {
public:
    base(){ a=5; cout<<"base a="<<a<<endl; }
protected:
    int a;
};
class base1:public base{
public:
    base1() { a=a+10; cout<<"base1 a="<<a<<endl; }
};
class base2:public base{
public:
    base2(){a=a+20; cout<<"base2 a="<<a<<endl;}
};
class derived:public base1,public base2{
public:
    derived() {
        cout<<"base1::a="<<base1::a<<endl;
        cout<<"base2::a="<<base2::a<<endl;
    }
};
main(){
    derived obj;
    return 0;
}
程序运行结果如下:
base a=5
base1 a=15
base a=5
base2 a=25
base1::a=15
base2::a=25

非虚基类的类层次图

image

2. 虚基类的概念

class derived:public base1,public base2{
public:
    derived()
    { cout<<"base1::a="<<a<<endl; }
}

可使用虚基类解决上例访问类base成员a的二义性问题。

虚基类的声明:

位置:定义派生类时声明。

其语法形式如下:

class 派生类名:virtual 继承方式 类名{
//…
}
例 虚基类的使用
#include <iostream.h>
class base {
public:
    base( ){ a=5; cout<<"base a="<<a<<endl;}
protected:
    int a;
};
class base1: virtual public base{
public:
    base1( ){ a=a+10; cout<<"base1 a="<<a<<endl;}
};
class base2: virtual public base{
public:
    base2( ){ a=a+20; cout<<"base2 a="<<a<<endl;}
};
class derived:public base1,public base2{
public:
    derived( ){ cout<<"derived a="<<a<<endl;}
};
main( ){
    derived obj;
    return 0;
}
程序运行结果如下:
base a=5
base1 a=15
base2 a=35
derived a=35

虚基类的类层次图

image

3. 虚基类的初始化

虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的调用顺序不同。在使用虚基类机制时应该注意以下几点:

  • (1) 如果在虚基类中定义有带形参的构造函数,并且没有定义缺省形参的构造函数,则整个继承结构中,所有直接间接派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。
  • (2) 建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略
  • (3) 若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数;
  • (4) 对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下;
  • (5) 对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下;
  • (6) 若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数。
class X:public Y,virtual public Z
{//….};
X one;
定义类X的对象one后,将产生如下的调用次序
Z();
Y();
X();
例:含有虚基类的派生类构造函数的执行顺序
#include<iostream.h>
class base {
public:
    base(int sa) {
        a=sa;
        cout<<"Constructing base"<<endl;
    }
private:
    int a;
};
class base1: virtual public base{
public:
    base1(int sa,int sb): base(sa) {
        b=sb;
        cout<<"Constructing baes1"<<endl;
    }
private:
    int b;
};
class base2: virtual public base{
public:
    base2(int sa, int sc): base(sa) {
        c=sc;
        cout<<"Constructing baes2"<<endl; }
private:
    int c;
};

虚基类的构造函数

为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这次虚基类构造函数必须只被调用一次。由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最派生类

C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用,如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。

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

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

class derived: public base1, public base2 {
public:
    derived(int sa, int sb,int sc,int sd):
    base(sa),base1(sa,sb),base2(sa,sc) {
        d=sd;
        cout<<"Constructing derived"<<endl;
    }
private:
    int d;
};
main(){
    derived obj(2,4,6,8);
    return 0;
}
Constructing base
Constructing base1
Constructing base2
Constructing derived

有关虚基类的两点说明:

  • 关键字virtual与继承方式关键字(public或private)的先后顺序无关紧要,它只说明是“虚拟继承”。下面二个虚继承方法是等价
class derived: virtual public base{
    //…
};
class derived: public virtual base{
    //…
};
  • 一个基类在作为某些派生类虚基类的同时,又作为另一些派生类的非虚基类,这种情况是允许的。下例说明了这个问题。
class B {
    //…
};
class X: virtual public B {
    //…
};
class Y: virtual public B {
    //…
};
class Z: public B {
    //…
};
class AA: public X,public Y,public Z {
    //…
};
posted @ 2020-02-28 12:20  鲸90830  阅读(705)  评论(0)    收藏  举报