14-3多重继承

多重继承

MI描述的是有多个直接基类的类。这同样描述的是is-a关系的继承关系。

公有MI有以下两个问题:

1.从两个基类继承了两个同名方法

2.从两个或更多基类中继承了同一个类的多个实例

14.3.1虚基类–解决基类问题

虚基类使从多个基类相同的类中派生出的对象只继承一个基类对象。

使用

在类声明中使用关键字virtual。

class Singer : virtual public Worker{...};
class Waiter : virtual public Worker{...};//两个基于虚基类的派生类
class SingingWaiter : public Singer, public Waiter {...};
//三代继承

这个SingingWaiter只有Worker对象的一个副本

新的函数构造规则

对于使用虚基类,第三代构造函数如果在初始化列表中调用第二代派生类构造函数,则会通过两条不同的途径将数据传递给虚基类对象。因此C++在基类是虚的时候禁止这种传参方式,而是采用直接显式地在初始化列表中调用虚基类的构造函数。

SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk,p), Singer(wk,v){}

此处使用Worker(wk)来初始化虚基类对象

注:对于虚基类,这种方式是合法的,对于非虚基类,这种方式是非法的。

14.3.2方法冲突–解决方法问题

除了修改构造函数,MI还需调整方法。

对于单继承,如果调用方法,则会使用继承的类中的方法,但对于MI来说,如果继承的两个类出现同名方法,不能确定使用哪一个方法,从而产生二义性。

解决方案

1.在成员名字前面加上类名和域解析符::,以显式地指明到底使用哪个类的成员,消除二义性

SingingWaiter newhere("Elise Hawks", 2005, 6, soprano);
newhere.Singer::Show(); //类名和域解析符

2.使用模块化方法重新组装函数

在基类和派生类中只给出自己成员的处理函数,再在三代派生中组装函数

案例

void Worker::Data()		//基类
{
...
}

void Waiter::Data() //派生1
{
...
}

void Singer::Data() //派生2
{
...
}

void SingingWaiter::Data() const //三代派生组装
{
Singer::Data();
Waiter::Data();
}

void SingingWaiter::Show() const //三代派生组装
{
Worker::Data();
Data();
}

3.将所有的数据组件设置为保护的而非私有的

总结案例

该案例总结地使用了上述方法

//workermi.h
#pragma once

#include<string>

class Worker
{
private:
std::string fullname;
long id;
protected:
virtual void Data() const; //虚方法,为派生方法做准备
virtual void Get();
public:
Worker() : fullname("no name"), id(0L) {};
Worker(const std::string & s, long n)
: fullname(s), id(n) {};
virtual ~Worker() = 0; //纯虚函数,本身无定义
virtual void Set() = 0;
virtual void Show() = 0;
};

class Waiter
{
private:
int panache;
protected:
void Data() const;
void Get();
public:
Waiter() : Worker(), panache(0) {}
Waiter(const std::string & s, long n, int p = 0)
: Worker(s,n), panache(p) {}
Waiter(const Worker & wk, int p = 0)
: Worker(wk), panache(p) {}
void Set();
void Show() const;
};

class Singer : virtual public Worker //公有派生,虚基类
{
protected:
enum{other, alto, contralto, soprano,
bass, baritone, tenor};
enum{Vtypes = 7};
void Data() const;
void Get();
private:
static char *pv[Vtypes];
int voice;
public:
Singer() : Worker(), voice(other) {}
Singer(const std::string & s, long n, int v = other)
: Worker(s,n), voice(v) {}
Singer(const Worker & wk, int v = other)
: Worker(wk), voice(v) {}
void Set();
void Show() const;
};

//三代派生类
class SingingWaiter : public Singer, public Waiter
{
protected:
void Data() const;
void Get();
public:
SingingWaiter() {};
SingingWaiter(const std::string & s, long n, int p = 0,
int v = other)
: Worker(s,n), Waiter(s,n,p), Singer(s,n,v) {}
SingingWaiter(const Worker & wk, int p = 0,
int v = other)
: Worker(wk), Waiter(wk,p), Singer(wk,v) {}
SingingWaiter(const Waiter & wt, int v = other)
: Worker(wt), Waiter(wt), Singer(wt,v) {}
SingingWaiter(const Singer & wt, int p = 0)
: Worker(wt), Waiter(wt, p), Singer(wt) {}
void Set();
void Show() const;
};
//workermi.cpp
#pragma once

#include"workmi.h"
#include<iostream>
using std::cout;
using std::cin;
using std::endl;

//work methods
Worker::~Worker() {}

//protected methods
void Worker::Data() const
{
cout<<"Name: "<<fullname<<endl;
cout<<"Employee ID: "<< id << endl;
}

void Worker::Get()
{
getline(cin, fullname);
cout<< "Enter worker's ID: ";
cin>>id;
while(cin.get()!='\n')
continue;
}

//Waiter methods
void Waiter::Set()
{
cout<<"Enter waiter's name: ";
Worker::Get();
Get();
}

void Waiter::Show() const
{
cout<< "Category: waiter\n";
Worker::Data();
Data();
}

//protected methods
void Waiter::Data() const
{
cout<<"Panache rating: "<< panache<<endl;
}

void Waiter::Get()
{
cout<<"Enter waiter's panache rating: ";
cin>>panache;
while(cin.get()!='\n')
continue;
}

//Singer methods

char * Singer::pv[Singer::Vtypes] = {"other", "alto", "contralro",
"soprano", "bass", "baritone", "tenor"};

void Singer::Set()
{
cout<<"Enter singer's name: ";
Worker::Get();
Get();
}

void Singer::Show() const
{
cout<< "Category: singer\n";
Worker::Data();
Data();
}

//protected methods
void Singer::Data() const
{
cout<<"Vocal range: "<<pv[voice] << endl;
}

void Singer::Get()
{
cout<<"Enter number for singer's vocal range:\n";
int i;
for(i = 0; i < Vtypes; i++)
{
cout<<i<<": "<<pv[i]<<" ";
if(i%4==3)
cout<<endl;
}
if(i%4 != 0)
cout<<'\n';
cin>>voice;
while(cin.get()!='\n')
continue;
}

//SingingWaiter methods
void SingingWaiter::Data() const
{
Singer::Data();
Waiter::Data();
}

void SingingWaiter::Get()
{
Waiter::Get();
Singer::Get();
}

void SingingWaiter::Set()
{
cout<<"Enter singing waiter's name: ";
Worker::Get();
Get();
}

void SingingWaiter::Show() const
{
cout<<"Category: singing waiter\n";
Worker::Data();
Data();
}

其它问题

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

当基类是虚基类时,派生类包含基类的一个子对象;当基类不是虚基类时,派生类包含多个子对象。

当混合使用时,虚基类共享一个对象,非虚基类对象数目递增。

2.虚基类和支配

在不使用虚基类的情况下,如果使用不同基类继承的同名成员,可能导致二义性。

但使用虚基类,如果在某个名称优先于其它名称的情况下,先使用这个名称。

派生类中的名称优于直接或间接祖先类中的相同名称