人小鬼不大

导航

 

  多继承时很容易产生命名冲突,即使小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示:

 

 

 类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自ABD,一份来自ACD。在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。例如在派生类D中访问间接基类A的成员,此时直接基类B和直接基类C中都存在A类的成员,导致编译器不清楚调用哪一个,就会出现命名冲突。解决办法:使用类名和域解析符指明来自哪一个类。如下:

{ 
    B::m_a = a; 
}

1、虚继承

  为了解决多继承时的命名冲突和冗余数据问题,C++提出了虚继承,使得在派生类中只保留一份间接基类的成员。在继承方式前面加上 virtual 关键字就是虚继承,如下:

class B: virtual public A{      //虚继承
   ...     
};

这样在上述图示的继承关系中,派生类D就不需要使用类名和域解析符去限定调用哪一路径的间接基类A的成员。虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),上述的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

注意:虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。

  C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。

2、虚基类成员的可见性

  在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。若虚基类的成员只被一条派生路径覆盖,那仍可以直接访问这个被覆盖的成员。若该成员被两条或多条路径覆盖了,那就不能直接访问,此时必须指明该成员属于哪个类。以上图为例,假设 B 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:

  • 如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 B 的成员,此时不存在二义性。
  • 如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
  • 如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。

3、虚继承时的构造函数

  在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。

  虚继承时构造函数的执行顺序与普通继承时不同:在最终派生类的构造函数调用列表中,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数;而对于普通继承,就是按照构造函数出现的顺序依次调用的。如下的执行顺序是一样的:

1 D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ }
2 D::D(int a, int b, int c, int d): B(90, b), C(100, c), A(a), m_d(d){ }

 

posted on 2020-02-16 17:02  人小鬼不大  阅读(450)  评论(0编辑  收藏  举报