多重继承 与 虚基类

使用多个基类的继承被称为多重继承(multiple inheritance,MI)

私有MI和保护MI表示has-a关系。

 

公有多重继承
    与单继承一样,公有MI表示的也是is-a关系。必须使用关键字public来限定每个基类。这是因为,除非特别指出,否则编译器将认为是私有派生:
class AB:public B,A {...}; // A is a private base

 

    假设定义一个抽象基类worker,并使用它派生出waiter类和singer类。然后便可以使用MI从waiter类和singer类派生出SingingWaiter类,这将导致MI的大多数麻烦。

 

从waiter类和singer类派生出SingingWaiter类:
class singingwaiter: public singer,public waiter{...};

 

1.有多少个worker
    因为waiter和singer都继承了worker组件,因此SingingWaiter将包含两个worker组件。
   

    通常可以将派生类对象的地址赋给基类基址,但是现在将出现二义性:
SingingWaiter ed;
worker * pw = &ed;
// ambiguous

   

    通常,这种赋值将把基类指针设置为派生对象中的基类对象的地址。但是ed中包含两个worker对象,有两个地址可供选择,所以应使用类型转换来制定对象:
worker *pw1=(waiter *) &ed; // the worker in waiter
worker *pw2=(singer *) &ed; // the worker in singer
这将使得使用基类指针来引用不同的对象(多态性)复杂化。

 

解决办法:虚基类(virtual base class)

 

虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。

通过在类声明中使用关键字virtual,可以使worker被用作singer和waiter的虚基类(virtual和public的次序无关紧要):
class singer: virtual public worker {...};
class waiter: public virtual worker {...};

 

然后,可以将singingwaiter类定义为:
class singingwaiter: public singer,public waiter {...};

   

     现在singingwaiter对象将只包含worker对象的一个拷贝。从本质上说,继承的singer和waiter对象共享一个worker对象,而不是各自引入自己的worker对象拷贝。

 

使用虚基类时的构造函数
   
    假设使用非虚基类。类A派生类B,类B再派生类C,当使用构造函数时,信息将自动传递:

class C: public B
{int c;
 public: C(int q=0,int m=0,int n=0):B(m,n){c=q;}
 ...
};

 

   这里,C类的构造函数使用值q,并将值m和n传递给B类的构造函数;而B类的构造函数使用值m,并将值n传递给A类的构造函数。

 

如果worker是虚基类,则这种信息自动传递将不起作用。例如:
singingwaiter(const worker & wk,int p=0,int v=singer::other)
             :waiter(wk,p),singer(wk,v){}
  // flawed

 

   存在的问题是,自动传递消息时,将通过2条不同的途径(waiter和singer)将wk传递给worker对象。为避免这种冲突,在基类是虚拟的时,禁止信息通过中间类自动传递给基类。因此,上述构造函数里wk参数中的信息将不会传递给子对象waiter和singer。编译器将使用worker的默认构造函数。

 

    如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显示地调用该虚基类的某个构造函数。

 

singingwaiter(const worker & wk,int p=0,int v=singer::other)
             :worker(wk),waiter(wk,p),singer(wk,v){}

上述代码将显示地调用构造函数worker(const worker &)。
这种用法对于虚基类是必须的;但对于非虚基类则是非法的。

 

2.哪个方法
    假设waiter和singer中定义了方法show()。在singingwaiter类中并未重新定义show()方法,又试图使用singingwaiter对象调用继承的show()方法:
singingwaiter nawhire("elise hawks",2005,6,soprano);
nawhire.show();
  // ambiguous

 

    对于单继承,如果没有重新定义show(),则将使用最近祖先中的定义。而在MI中,每个直接祖先都有一个show()函数,这使得上述调用存在二义性。

 

可以使用作用域解析操作符来澄清编程者的意图:
singingwaiter nawhire("elise hawks",2005,6,soprano);
nawhire.singer::show();
   // use singer version

 

    不过更好的方法是在singingwaiter中重新定义show(),并指出要使用哪个show()。

 

    在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则。另外,如果在编写这些类时没有考虑到MI,则还可能需要重新编写它们。

 

混合使用虚基类和非虚基类


    假设类B被用作类C和D的虚基类,同时被用作类X和Y的非虚基类,而类M是从C、D、X和Y派生而来的。在这种情况下,类M从虚拟派生祖先(C和D)那里共继承了一个B类子对象,并从每一个非虚拟派生祖先(X和Y)分别继承了一个B类子对象。因此,它包含三个B类子对象。
    当类通过多条虚拟途径和非虚拟途径继承某个特定的基类时,该类将包含一个表示所有的虚拟途径的基类子对象和分别表示各条非虚拟途径的多个基类子对象。

 

虚基类和支配
    使用虚基类将改变C++解析二义性的方式。使用非虚基类时,规则很简单。如果类从不同的类那里继承了两个或更多的同名成员(数据或方法),则使用该成员名时,如果没有用类名进行限定,将导致二义性。但如果使用的是虚基类,则这样做不一定会导致二义性。
    在这种情况下,如果某个名称优先于(dominates)其他所有名称,则使用它时,即便不是用限定符,也不会导致二义性。

 

成员名如何优先:
(派生类中的名称优先于直接或间接祖先类中的相同名称)
class B
{public:short q();
 ...};

class C: virtual public B
{public: long q();int omb();
 ...};

class D: public C
{ ... };

class E: virtual public B
{private:int omb();
 ...};

class F: public D,public E
{ ... };

 

    类C中的q()定义优先于类B中的q()定义,因为类C是从类B派生而来的。因此F中的方法可以使用q()来表示C::q()。而任何一个omb()定义都不优先于其他omb()定义,因为C和E都不是对方的基类。所以,在F中使用非限定的omb()将导致二义性。
    虚拟二义性规则与访问规则无关,也就是说,即使E::omb()是私有的,也不能在F类中直接访问,但使用omb()仍将导致二义性。同样,即使C::q()是私有的,它也将优先于B::q()。在这种情况下,可以在类F中调用B::q(),但如果不限定q(),则意味着要调用不可访问的C::q()。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值