C++二义性

名字间隔概念

一个名字的间隔就是某个数据的名字从继承层次中首次出现到达最后派生类时中间隔了多少相同的名字。间隔越少,这个名字的优先级越高。当然直接在最终类里面声明的名字具有最高的优先级。

class A { void print(); }; class B : public A { void print(); }; class C : public C { void print(); };

如果使用C的对象,那么 A 中的 pirnt 与 C 间隔最大,C 中的 print 与 C 的间隔最短,所以如果直接调用 C 对象的 print 函数,那么将调用 C 版本的 print。

如果 C 没有定义一个 print 函数,那么 B 中的 print 函数与 C 间隔最小,那么调用 C 对象的 print 函数时,将调用 B 版本的 print 函数。

二义性概念

如果存在两个及其以上的名字距离最终派生类的距离最短(长度一样),那么,根据刚才由名字间隔定义的优先级别,在直接调用这个派生类对象的相应数据时,便不知道该调用哪个版本了(注意直接两个字,因为可以通过二元::来分辨具体的版本以调用,所以即使名字存在二义性,如果未调用这些名字,编译器可能不会报错)。

二义性发生的情况

  1. 多继承的时候继承了两个间隔一样的名字
    1. 来自两个基类各自的声明
    2. 也可能来自两个基类继承自更高层次的同一基类(菱形继承)
    3. 也可能其中一个名字来自基类声明,另一个名字来自另一个基类对更高层次基类的继承
  2. 只要同时存在两个及其以上具有如果存在两个及其以上的名字距离最终派生类的距离最短(长度一样),那么就存在二义性。

案例

来自两个基类各自声明:

class B1 { void print(); }; class B2 : { void print(); }; class C : public B1, public B2 { };

菱形继承:

class A { void print(); }; class B1 : public A { }; class B2 : public A { }; class C : public B1, public B2 { };

其中一个名字来自基类声明,另一个名字来自另一个基类对更高层次基类的继承:

class A { void print(); }; class B1 : public A { }; class B2 : { void print(); }; class C : public B1, public B2 { };

虽然 A 版本的 print 是通过 B1 到达 C 的,但是 A->B1->C 的过程中,A 版本的 print 与 C 之间并没有间隔其他的 print,这与 B2 版本的 print 一样,所以他们具有相同的名字间隔,因此具有二义性。

解决方案

在最终派生类中定义一个相同名字的成员,这样这个名字距离最终派生类最近,所以就会调用这个名字下的数据(通常教材里叫做这个名字把其他名字隐藏了)。这个名字(如果是函数)你可以自己定义新的方法,也可以通过二元::调用你已知的存在二义性的名字中的某一个(注意:如果你选择的调用版本不是该派生类的直接基类,那么该如何调用呢?比如 A->B->C,那么从 C 的对象 c 调用 A 的 print 函数,c.A::print() 是否可行?)

使用虚继承(针对菱形继承等):回想一下虚继承和普通继承,通过虚继承的方法可以消除重复副本带来的二义性问题。比如在某一继承层次上,这个某两个名字具有二义性,然而顺着继承层次向上分析,却发现这两个名字其实是同一个东西的两个副本,这个时候如果使用虚继承,那么就使得这两个副本变为一个副本(准确地说,两个副本都没有了,因为只存在他们公共基类的那份数据,虚基类得到的不过是使用权)。