从C到C++___类继承(五)多重继承

类继承(五) 多重继承

多重继承(MI)指的是有多个直接基类的继承。和单继承一样,公有MI表示的也是 is-a 关系。例如我们可以从WaiterSinger派生出SingingWaiter:
class SingingWaiter :public Waiter,public Singer{...};
这里这个关键词public必须每个基类都要用,如果不加修饰词,默认是私有继承。
在之前我提过使用私有MI和保护MI可以实现 has-a 关系。但是我们重点是公有MI。

MI可能会给程序员带来很多问题。主要的问题是:从不同的基类继承同名方法;从两个或者更多相关基类那里继承同一个类的多个实例。为解决这些问题,需要使用一些新规则和不同的语法。MI比单继承困难许多。很多C++用户强烈反对使用MI,有一些人甚至希望删除MI。

1. 一个例子

设计一个抽象基类Worker,然后从中派生出WaiterSinger。然后我们将这两个类使用公有MI派生出SingingWaiter

如下是从Worker中派生出WaiterSinger的代码

//多重继承1.h
#ifndef WORKER0_H_
#define WORERK0_H_

#include<string>
using std::string;
class Worker
{
    private:
        string fullname;
        long id;
    public:
        Worker():fullname("no one"),id(0l){}
        Worker(const string & s,long i):fullname(s),id(i){}
        virtual ~Worker(){};
        virtual void Set()=0;
        virtual void Show() const;
};

class Waiter:public Worker
{
    private:
        int panache;
    public:
        Waiter():Worker(),panache(0){}
        Waiter(const string &s,long i,int p):Worker(s,i),panache(p){}
        virtual void Set();
        virtual void Show() const;
};
class Singer:public Worker
{
    protected:
        enum{other,alto,contralto,soprano,bass,baritone,tenor};
        enum{Vtypes=7};
    private:
        static const char * pv[Vtypes];
        int voice;
    public:
        Singer():Worker(),voice(other){}
        Singer(const string &s,long i,int v):Worker(s,i),voice(v){}
        virtual void Set();
        virtual void Show() const;
};
#endif
//多重继承1.cpp
#include"多重继承1.h"
#include<iostream>
using std::cout;
using std::cin;
using std::endl;

void Worker::Set()
{
    cout<<"Enter worker's name: ";
    std::getline(cin,fullname);
    cout<<"Worker's id: ";
    cin>>id;
    cin.ignore();
}
void Worker::Show() const
{
    cout<<"Name: "<<fullname<<endl;
    cout<<"ID: "<<id<<endl;
}
void Waiter::Set()
{
    Worker::Set();
    cout<<"Enter the waiter's panache rating: ";
    cin>>panache;
    cin.ignore();
}
void Waiter::Show()const
{
    cout<<"Category: waiter"<<endl;
    Worker::Show();
    cout<<"Panache rating: "<<panache<<endl;
}
const char* Singer::pv[Vtypes]={"other","alto","contralto","soprano","bass","baritone","tenor"};
void Singer::Set()
{
    Worker::Set();
    cout<<"Enter number for singer's vocal range:\n";
    int i;
    for(i=0;i<Vtypes;i++)
    {
        cout<<i<<": "<<pv[i]<<" ";
        if((i+1)%4==0)
            cout<<endl;
    }
    if(Vtypes%4!=0)
        cout<<endl;
    while (!(cin>>voice) || (voice<0 || voice>=Vtypes))
    {
        cout<<"Please enter a value >=0 and < "<<Vtypes<<endl;
    }
    cin.ignore();
    
}
void Singer::Show() const
{
    cout<<"Category: Singer"<<endl;
    Worker::Show();
    cout<<"Vocal range: "<<pv[voice]<<endl;
}

有几点注意一下:

  • 对于抽象基类中的纯虚函数,我们可以给它定义,但是不能在类声明中定义,否则会报pure-specifier on function-definition错误。
  • 获得一个类作用域中的常数我们一般使用enum成员,或者使用静态const变量。
  • 抽象基类不能生成对象,但是我们可以使用抽象基类指针或引用指向其派生类对象。

如下是测试代码:

//多重继承1mian.cpp
#include<iostream>
#include"多重继承1.h"
const int LIM=4;

int main()
{
    Waiter bob("Bob Apple",314l,5);
    Singer bev("Beverly Hills",522L,3);
    Waiter w_temp;
    Singer s_temp;

    Worker *pw[LIM]={&bob,&bev,&w_temp,&s_temp};
    pw[2]->Set();
    pw[3]->Set();

    for(int i=0;i<LIM;i++)
    {
        pw[i]->Show();
        std::cout<<std::endl;
    }
}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 多重继承1 .\多重继承1.cpp .\多重继承1main.cpp
PS D:\study\c++\path_to_c++> .\多重继承1.exe
Enter worker's name: Waldo Dropmaster
Worker's id: 442
Enter the waiter's panache rating: 3
Enter worker's name: Sylvie Sirenne
Worker's id: 555
Enter number for singer's vocal range:
0: other 1: alto 2: contralto 3: soprano
4: bass 5: baritone 6: tenor
3
Category: waiter
Name: Bob Apple
ID: 314
Panache rating: 5

Category: Singer
Name: Beverly Hills
ID: 522
Vocal range: soprano

Category: waiter
Name: Waldo Dropmaster
ID: 442
Panache rating: 3

Category: Singer
Name: Sylvie Sirenne
ID: 555
Vocal range: soprano

2. 虚基类

假设我们从SingerWaiter公有派生出SingingWaiter:
class SingingWaiter:public Singer,public Waiter{...}
但是我们现在出现了一个问题:由于SingerWaiter都包含一个Worker子对象,因此SingingWaiter会包含两个Worker组件。

  • 这将会出现问题
SingingWaiter ed;
Worker *p=&ed;

上面这段代码会出现ambiguous的错误。为了改进上述错误,我们可以这样修改代码:

SingingWaiter ed;
Worker *p1=(Singer*)&ed;
Worker *p2=(Waiter*)&ed;

由于ed中存在两个Worker组件,我们必须显式指出,你需要的组件是哪个。

  • 可以前瞻性的看,为什么我们需要2个Worker组件,我们可不可以只包含一个Worker组件?

    C++引进了一种新技术–虚基类

虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。我们可以通过在类声明中使用关键词virtual,将Worker作为SingerWaiter的虚基类(virtualpublic的次序无所谓),然后从SingerWaiter中公有派生出SingingWaiter

class Singer:public virtual Worker{...};
class Waiter:virtual public Worker{...};
class SingingWaiter:public Singer,public Waiter{...};

现在SingingWaiter对象只包含Worker对象的一个副本。本质上来说,继承的SingerWaiter对象共享一个Worker对象,而不是各自引用自己的Worker对象副本。

2.1 新的构造函数的规则

  • 我们先复习一下旧的构造函数规则
class A
{
    int a;
public:
    A(int n=0):a(n){}
};
class B:public A
{
    int b;
public:
    B(int m=0,int n=0):A(n),b(m){}
};
class C:public B
{
    int c;
public:
    C(int q=0,int m=0,int n=0):B(m,n),c(q){}
};

上面这段代码中,C的构造函数只能调用B的构造函数,而B的构造函数只能调用A的构造函数。记住,只能调用直接基类的构造函数。例如,在C中调用A的构造函数就是错误的。

  • 为什么只能调用直接基类的构造函数?

构造函数的接口不能被继承,但是它的实现可以被继承。(也就是说,派生类相当于把基类的构造函数放到了私有部分),例如A是B的基类,B是C的基类。由于B没有继承A的构造函数接口,所以C继承B的时候,A的构造函数相当于在B的私有部分,既不能继承接口也不能继承实现,所以在C中无法调用A的构造函数。

插一句,

class H 
{
    public:
    int dad();
};
class G :public H
{
    void q();
};
class F:private G
{
    ...
};

上面这段代码中,在F中可以使用H::dad(),这是因为G公有继承了H的接口,而F继承了G的实现,从而继承了H::dad()在F中可用,但是它不是接口,也就是说,G的对象,可以调用dad(),而F的对象不能调用
dad()。在F中不能使用G::q(),这是因为,私有数据不会被继承。

  • 新的构造函数规则
class A
{
    int a;
public:
    A(int n=0):a(n){}
};
class B:public virtual A
{
    int b;
public:
    B(int m=0,int n=0):A(n),b(m){}
};
class C:public B
{
    int c;
public:
    C(int q=0,int m=0,int n=0):B(m,n),c(q){}
};

但是,如果我将A改成虚基类,class B:public virtual A,那么我们就可以在C中调用A的构造函数。因为在C中,现在包含了两个直接子对象,一个是不包含A的B对象,一个是A对象。所以说,间接虚拟基A相当于成为了C的直接基类。进一步的,我们可以在C中使用A的方法,而且C也继承了A的接口。可以看一下,虚基类和非虚基类的那张图就明白了。

回到SingingWaiter问题。
我们试着构建SingingWaiter的构造函数。
我们的一种想法是:
SingingWaiter(const string &s,long i,int p=0,int v=other):Waiter(s,i,p),Singer(s,i,v){};

这种方法存在问题:WaiterSinger的构造函数都可以给Worker传递参数,也就是说有两个途径传参给Worker,就会产生冲突。为了避免这种冲突,在基类是虚的时,禁止通过中间类自动传递给基类。因此上面那句代码,si不会传递给Worker对象,在这种情况下,编译器会自动调用Worker的默认构造函数。

在上面我们说过,间接虚基相当于是直接基类,我们可以这样写:
SingingWaiter(const string &s,long i,int p=0,int v=other):Worker(s,i),Waiter(s,i,p),Singer(s,i,v){};

上面这种写法是合法的,对于Worker是虚基类是合法的,如果它不是虚基类则是非法的,因为它不是直接基类。

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

插一句,在设计一个抽象基类Worker,然后从中派生出WaiterSinger时,由于Worker是抽象基类,它不可能创造对象,所以我们在给Singert编写构造函数的时候,我们没有写Singer(const Worker &w,int v);这样的构造函数,但是我们现在要从WaiterSinger派生出SingingWaiter时,我们就需要编写这样的构造函数SingingWaiter(const Waiter & w,int v):Worker(w),Waiter(w),Singer(w,v){};这里出现了我们需要调用Singer(w,v)的构造函数,这就意味着在编写Singer类时,Singer(const Worker &w,int v);的构造函数是有必要的。

2.2 使用哪个方法

如果我们没有在SingingWaiter中,重新定义Show()方法,那么

SingingWaiter w("Elis",2005,6,3);
w.Show();

这段代码是错误的。如果是单继承,那么这个Show()默认是基类的接口,因为公有继承继承基类接口。而现在是公有MI,就会出现问题,我们知道SingingWaiter有2个直接基类,1个间接虚基类,他们中都有Show()接口,那么上面那段代码就会ambiguous错误。

那么我们只好采用,作用域解析符来澄清:

SingingWaiter w("Elis",2005,6,3);
w.Singer::Show();
w.Waiter::Show();
w.Worker::Show();

当然了,我们需要在SingingWaiter中,重新定义Show()方法,但是问题在于我们无法直接访问,WorkerSingerWaiter中的私有数据,我们只能通过它们中的接口来访问数据,但是问题还是出现了

void SingingWaiter::Show() const
{
    Singer::Show();
    Waiter::Show();
}

上面这种方法将会显式名字和ID两次,不是完美的。

这就意味着,我们必须重新修改基类的接口,属实是麻烦。
总之,在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则。如果在编写这些类时没有考虑MI,则可能需要重新编写它们

2.3 实现1

//多重继承2.h
#ifndef aa
#define aa

#include<string>
using std::string;
class Worker
{
    private:
        string fullname;
        long id;
    protected:
        virtual void Data() const;
        virtual void Get();
    public:
        Worker():fullname("no one"),id(0l){}
        Worker(const string & s,long i):fullname(s),id(i){}
        virtual ~Worker(){};
        virtual void Set()=0;
        virtual void Show()const=0;
};

class Waiter:public virtual Worker
{
    private:
        int panache;
    protected:
        virtual void Data() const;
        virtual void Get();
    public:
        Waiter():Worker(),panache(0){}
        Waiter(const string &s,long i,int p=0):Worker(s,i),panache(p){}
        Waiter(const Worker &w,int p=0):Worker(w),panache(p){}
        virtual void Set();
        virtual void Show() const;
};
class Singer:public virtual Worker
{
    protected:
        enum{other,alto,contralto,soprano,bass,baritone,tenor};
        enum{Vtypes=7};
        virtual void Data() const;
        virtual void Get();
    private:
        static const char * pv[Vtypes];
        int voice;
    public:
        Singer():Worker(),voice(other){}
        Singer(const string &s,long i,int v=other):Worker(s,i),voice(v){}
        Singer(const Worker &w,int v=other):Worker(w),voice(v){}
        virtual void Set();
        virtual void Show() const;
};
class SingingWaiter:public Waiter,public Singer
{
    protected:
        virtual void Data() const;
        virtual void Get();
    public:
        SingingWaiter():Worker(),Waiter(),Singer(){}
        SingingWaiter(const string &s,long i,int p=0,int v=other):Worker(s,i),Waiter(s,i,p),Singer(s,i,v){};
        SingingWaiter(const Waiter &w,int v=other):Worker(w),Waiter(w),Singer(w,v){}
        SingingWaiter(const Singer &s,int p=0):Worker(s),Singer(s),Waiter(s,p){}
        virtual void Set();
        virtual void Show() const;
};
#endif
//多重继承2.cpp
#include"多重继承2.h"
#include<iostream>
using std::cout;
using std::cin;
using std::endl;

//Work
void Worker::Data() const
{
    cout<<"Name: "<<fullname<<endl;
    cout<<"ID: "<<id<<endl;
}
void Worker::Get()
{
    std::getline(cin,fullname);
    cout<<"Enter worker's id: ";
    cin>>id;
    cin.ignore();
}
//Waiter
void Waiter::Data() const
{
    cout<<"Panache rating: "<<panache<<endl;
}
void Waiter::Get()
{
    cout<<"Enter the waiter's panache rating: ";
    cin>>panache;
    cin.ignore();
}

void Waiter::Show()const
{
    cout<<"Category: waiter"<<endl;
    Worker::Data();
    Data();
}
void Waiter::Set()
{
    cout<<"Enter waiter's name: ";
    Worker::Get();
    Get();
}
//Singer
const char* Singer::pv[Vtypes]={"other","alto","contralto","soprano","bass","baritone","tenor"};
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+1)%4==0)
            cout<<endl;
    }
    if(Vtypes%4!=0)
        cout<<endl;
    while (!(cin>>voice) || (voice<0 || voice>=Vtypes))
    {
        cout<<"Please enter a value >=0 and < "<<Vtypes<<endl;
    }
    cin.ignore();
}
void Singer::Show() const
{
    cout<<"Category: Singer"<<endl;
    Worker::Data();
    Data();
}
void Singer::Set()
{
    cout<<"Enter singer's name: ";
    Worker::Get();
    Get();
}
//SingingWaiter
void SingingWaiter::Data() const
{
    Singer::Data();
    Waiter::Data();
}
void SingingWaiter::Get()
{
    Waiter::Get();
    Singer::Get();
}
void SingingWaiter::Show() const
{
    cout<<"Category: Singing waiter\n";
    Worker::Data();
    Data();
}
void SingingWaiter::Set()
{
    cout<<"Enter singing waiter's name: ";
    Worker::Get();
    Get();
}
//多重继承3main.cpp
#include<iostream>
#include<cstring>
#include"多重继承2.h"
const int SIZE=5;

int main()
{
    using std::cin;
    using std::cout;
    using std::endl;
    using std::strchr;

    Worker* lolas[SIZE];
    int ct;
    for(ct=0;ct<SIZE;ct++)
    {
        char choice;
        cout<<"Enter the employee category:\n"
            <<"w: waiter s: singer t: singing waiter q: quit\n";
      
        while (!(cin>>choice) || strchr("wstq",choice)==NULL)
        {
            cin.ignore();
           cout<<"Please enter a w,s,t,q: ";
        }
        if(choice=='q')
            break;
        switch (choice)
        {
        case 'w':lolas[ct]=new Waiter;
            break;
        case 's':lolas[ct]=new Singer;
            break;
        case 't':lolas[ct]=new SingingWaiter;
            break;
        }
        cin.ignore();//这是因为cin>>choice完成后,缓冲区会有一个换行符,我们必须吸收掉,以免影响Set()
        lolas[ct]->Set();
    }
    cout<<"\n Here is your staff:\n";
    for(int i=0;i<ct;i++)
    {
        cout<<endl;
        lolas[i]->Show();
    }

    for(int i=0;i<ct;i++)
        delete lolas[i];
        
    cout<<"Bye!\n";

}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 多重继承2 .\多重继承2.cpp .\多重继承2main.cpp
PS D:\study\c++\path_to_c++> .\多重继承2.exe
Enter the employee category:
w: waiter s: singer t: singing waiter q: quit
w
Enter waiter's name: Wally Slipshod
Enter worker's id: 1040
Enter the waiter's panache rating: 4
Enter the employee category:
w: waiter s: singer t: singing waiter q: quit
s
Enter singer's name: Sinclair Parma
Enter worker's id: 1044
Enter number for singer's vocal range:   
0: other 1: alto 2: contralto 3: soprano 
4: bass 5: baritone 6: tenor 
5
Enter the employee category:
w: waiter s: singer t: singing waiter q: quit
t
Enter singing waiter's name: Natasha Gargalova
Enter worker's id: 1021
Enter the waiter's panache rating: 6
Enter number for singer's vocal range:   
0: other 1: alto 2: contralto 3: soprano 
4: bass 5: baritone 6: tenor
3
Enter the employee category:
w: waiter s: singer t: singing waiter q: quit
q

 Here is your staff:

Category: waiter
Name: Wally Slipshod
ID: 1040
Panache rating: 4

Category: Singer
Name: Sinclair Parma
ID: 1044
Vocal range: baritone

Category: Singing waiter
Name: Natasha Gargalova
ID: 1021
Vocal range: soprano
Panache rating: 6
Bye!

2.4 实现2

既然无法直接访问基类的私有数据项,那我们就把基类的数据设置成protected的,那么派生类SingingWaiter就可以直接访问基类的数据了。

//多重继承3.h
#ifndef aa
#define aa

#include<string>
using std::string;
class Worker
{
    protected:
        string fullname;
        long id;
    public:
        Worker():fullname("no one"),id(0l){}
        Worker(const string & s,long i):fullname(s),id(i){}
        virtual ~Worker(){};
        virtual void Set()=0;
        virtual void Show()const=0;
};

class Waiter:public virtual Worker
{
    protected:
        int panache;
    public:
        Waiter():Worker(),panache(0){}
        Waiter(const string &s,long i,int p=0):Worker(s,i),panache(p){}
        Waiter(const Worker &w,int p=0):Worker(w),panache(p){}
        virtual void Set();
        virtual void Show() const;
};
class Singer:public virtual Worker
{
    protected:
        enum{other,alto,contralto,soprano,bass,baritone,tenor};
        enum{Vtypes=7};
        static const char * pv[Vtypes];
        int voice;
    public:
        Singer():Worker(),voice(other){}
        Singer(const string &s,long i,int v=other):Worker(s,i),voice(v){}
        Singer(const Worker &w,int v=other):Worker(w),voice(v){}
        virtual void Set();
        virtual void Show() const;
};
class SingingWaiter:public Waiter,public Singer
{
    public:
        SingingWaiter():Worker(),Waiter(),Singer(){}
        SingingWaiter(const string &s,long i,int p=0,int v=other):Worker(s,i),Waiter(s,i,p),Singer(s,i,v){};
        SingingWaiter(const Waiter &w,int v=other):Worker(w),Waiter(w),Singer(w,v){}
        SingingWaiter(const Singer &s,int p=0):Worker(s),Singer(s),Waiter(s,p){}
        virtual void Set();
        virtual void Show() const;
};
#endif
#include"多重继承3.h"
#include<iostream>
using std::cout;
using std::cin;
using std::endl;

//Worker
void Worker::Set()
{
    cout<<"Enter worker's name: ";
    std::getline(cin,fullname);
    cout<<"Worker's id: ";
    cin>>id;
    cin.ignore();
}
void Worker::Show() const
{
    cout<<"Name: "<<fullname<<endl;
    cout<<"ID: "<<id<<endl;
}
//Waiter
void Waiter::Set()
{
    Worker::Set();
    cout<<"Enter the waiter's panache rating: ";
    cin>>panache;
    cin.ignore();
}
void Waiter::Show()const
{
    cout<<"Category: waiter"<<endl;
    Worker::Show();
    cout<<"Panache rating: "<<panache<<endl;
}
//Singer
const char* Singer::pv[Vtypes]={"other","alto","contralto","soprano","bass","baritone","tenor"};
void Singer::Set()
{
    Worker::Set();
    cout<<"Enter number for singer's vocal range:\n";
    int i;
    for(i=0;i<Vtypes;i++)
    {
        cout<<i<<": "<<pv[i]<<" ";
        if((i+1)%4==0)
            cout<<endl;
    }
    if(Vtypes%4!=0)
        cout<<endl;
    while (!(cin>>voice) || (voice<0 || voice>=Vtypes))
    {
        cout<<"Please enter a value >=0 and < "<<Vtypes<<endl;
    }
    cin.ignore();
    
}
void Singer::Show() const
{
    cout<<"Category: Singer"<<endl;
    Worker::Show();
    cout<<"Vocal range: "<<pv[voice]<<endl;
}
//SingingWaiter
void SingingWaiter::Set()
{
   Singer::Set();
   cout<<"Enter the waiter's panache rating: ";
    cin>>panache;
    cin.ignore();
}
void SingingWaiter::Show()const
{
    cout<<"Category: Singing waiter"<<endl;
    Worker::Show();
    cout<<"Vocal range: "<<pv[voice]<<endl;
    cout<<"Panache rating: "<<panache<<endl;
}

3.MI的其他问题

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

    例如,B是C和D的虚基类,同时B是X和Y的非虚基类,M是从C、D、X和Y派生而来的。那么M类从虚派生祖先(C和D)中共继承了一个B类子对象,并从每一个非虚基类祖先(X和Y)那分别继承了一个B类子对象。因此,它包含3个B类子对象。

  • 虚基类和支配

    使用非基类时,如果类从不同的类那里继承了两个或多个同名成员,那么使用这些成员的时候,如果没有使用类名进行限定,将会导致二义性。如果使用虚基类,则这样做不一定会导致二义性,因为这时,可能某个名称优先于其他所有名称,即便不使用限定符,也不会导致二义性。

class B
{
public:
    short q();
};

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

class D:public C
{
    ...
};

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

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

类C的q()优先级高于类B中的q(),因此在F类中,可以直接使用q()表示C::q()。另一方面,仍和一个omg()都不优先于另一个omg(),因为C和E都不是对方的基类。所以在F中使用omg()会导致二义性。而且虚二义性规则和访问规则无关,也就是说,E::omg()是私有的,不能在F中直接访问,但是使用omg()仍将导致二义性。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值