C++ 多重继承

一、简述

多重继承:(multiple inheritance, MI)使用多个基类的继承被称为多重继承。且描述的是有多个直接基类的类。与单继承一样,公有多重继承表示的也是is-a关系。

公有多重继承:必须使用关键字public来限定每一个基类。这是因为,除非特别指出,否则编译器将认为是私有派生,示例如下:

// 使用关键字public限定每一个基类
class SingWaiter : public Waiter, public Singer
{
};

// 不使用关键字public限定基类
// Singer是一个私有基类
class SingWaiter : public Waiter, Singer
{
};

私有多重继承和保护多重继承:使用关键字private和关键字protected来限定基类,表示的是has-a关系。

下面着重介绍公有多重继承

二、公有多重继承

2.1 公有多重继承问题

多重继承的使用会产生一些新问题。其中有两个主要的问题需要引起关注:

  • 问题1: 从两个不同的基类继承同名方法。
  • 问题2: 从两个或更多相关基类那里继承同一个类的多个实例。
2.2 多重继承示例

通过多重继承示例了解多重继承,并了解多重继承的问题及解决问题的方式:

示例创建说明:

定义一个抽象基类Worker,并使用抽象基类派生出Waiter和Singer类。

使用多重继承(MI)从Waiter类和Singer类派生出SingingWaiter类。同时使用独立的派生来使基类Worker被继承。

2.2.1 基类与派生类声明

下面是抽象基类Worker的声明:

// 抽象基类Worker
class Worker
{
private:
    std::string fullname;
    long id;

public:
    Worker();
    Worker(const std::string& s, long n);

    virtual ~Worker() = 0;
    virtual void set();
    virtual void show() const;
};

以下示例是派生类Waiter与派生类Singer的声明:

// 派生类Waiter
class Waiter : public Worker
{
private:
    int panache;

public:
    Waiter();
    Waiter(const std::string& s, long n, int p = 0);
    Waiter(const Worker& wk, int p = 0);

    void set();
    void show() const;
};

// 派生类Singer
class Singer : public Worker
{
protected:
    // 一个枚举使用符号常量alto、contralto等表示声音类型
    enum
    {
        other,
        alto,
        contralto,
        soprano,
        bass,
        baritone,
        tenor,
    };

    enum
    {
        Vtypes = 7,
    };

private:
    // 静态数组pv存储指向相应C-风格字符串的指针
    static char* pv[Vtypes];
    int voice;

public:
    Singer();
    Singer(const std::string& s, long n, int v = other);
    Singer(const Worker& wk, int v = other);

    void set();
    void show() const;
};
2.2.2 基类与派生类的实现

下面是抽象基类的实现,当然抽象基类不能实例化对象:

// 抽象基类Worker
Worker::Worker()
    : fullname("no one")
    , id(0L)
{

}

Worker::Worker(const std::string &s, long n)
    : fullname(s)
    , id(n)
{

}

Worker::~Worker()
{

}

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

void Worker::show() const
{
    std::cout << "Name: " << fullname << std::endl;
    std::cout << "Employee ID: " << id << std::endl;
}

下面示例代码是派生类waiter和singer的实现:

// 派生类Waiter
Waiter::Waiter()
    : Worker()
    , panache(0)
{

}

Waiter::Waiter(const std::string &s, long n, int p)
    : Worker(s, n)
    , panache(p)
{

}

Waiter::Waiter(const Worker &wk, int p)
    : Worker(wk)
    , panache(p)
{

}

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

void Waiter::show() const
{
    std::cout << "Category: waiter\n";
    Worker::show();
    std::cout << "Panache rating: " << panache << "\n";
}

// 派生类Singer
char* Singer::pv[Vtypes] = {"other", "aito", "contralto", "soprano", "bass", "baritone", "tenor"};

Singer::Singer()
    : Worker()
    , voice(other)
{

}

Singer::Singer(const std::string &s, long n, int v)
    : Worker(s, n)
    , voice(v)
{

}

Singer::Singer(const Worker &wk, int v)
    : Worker(wk)
    , voice(v)
{

}

void Singer::set()
{
    Worker::set();
    std::cout << "Enter number of singer's vocal range:\n";
    int i;
    for (i = 0;i < Vtypes; ++i) {
        std::cout << i << ": " << pv[i] << "  ";
        if (i % 4 == 3) {
            std::cout << std::endl;
        }
    }

    if (i % 4 != 0) {
        std::cout << std::endl;
    }

    while (std::cin >> voice && (voice < 0 || voice >= Vtypes)) {
        std::cout << "Please enter a value >= 0 and < " << Vtypes << std::endl;
    }
    while (std::cin.get() != '\n') {
        continue;
    }
}

void Singer::show() const
{
    std::cout << "Category: singer\n";
    Worker::show();
    std::cout << "Vocal range: " << pv[voice] << "\n";
}
2.2.3 设计疑问点

2.2.2章节的设计看起来可行,可以使用Waiter指针调用Waiter::Show()和Waiter::Set(),可以使用Singer指针调用Singer::Show()和Singer::Set()。然而假设再使用Waiter和Singer类派生出SingingWaiter类,将会产生如下问题:

  • 有多少Worker对象?
  • 调用哪个基类的方法?
2.3 带着问题去深入

根据2.2.3描述的再派生出一个派生类产生的问题,我们再深入理解,假设从Waiter类和Singer类公有派生出SingingWaiter类,如下代码所示:

// 再派生出的派生类SingingWaiter
class SingingWaiter : public Singer, public Waiter
{
    // TODO: do something
};
2.3.1 多个Worker问题

因为Waiter类和Singer类都继承了一个抽象基类Worker,因此SingingWaiter类将包含两个Worker组件,这将引起问题。例如,通常可以将派生类对象的地址赋给基类指针,但现在将出现二义性,如下示例代码:

SingingWaiter ed;
// 通常,这种赋值将基类指针设置为派生类对象中的基类对象的地址
Worker* pw = &ed;

由于ed对象中有两个Worker对象,有两个地址可供选择,所以应该使用类型转换来指定对象

// 基类指针指向转换为Waiter*的地址
Worker *pw = (Waiter*)&ed;
// 基类指针指向转换为Singer*的地址
Worker *pw = (Singer*)&ed;

问题1:

上述情况,将使得使用基类指针来引用不同的对象(多态性)复杂化。包含两个Worker的对象拷贝还会导致其它问题。

真正的问题是:为什么需要Worker对象的两个拷贝?

处理1:

C++引入多重继承的同时,引入了一种新技术虚基类(virtual base class),使得多重继承成为可能。

2.3.2 虚基类

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

虚基类方式: 通过在类声明继承时使用virtual关键字,virtual与public的次序无关紧要,不分先后,习惯性写法是virtual在前,如下代码示例:

// Waiter声明为虚基类继承
class Waiter : virtual public Worker
{
    // TODO: do something
};

// Singer声明为虚基类继承
class Singer : virtual public Worker
{
    // TODO: do something
};

上述派生类声明后,就可以将由这两个派生类再派生出的派生类声明为如下形式:

// 1、现在SingingWaiter对象将只包含Worker对象的一个副本。
// 2、本质上讲,继承Waiter和Singer对象共享一个Worker对象,而不是各自引入自己的Worker对象副本
// 3、因为SingingWaiter现在只包含了一个Worker子对象,所以可以使用多态。
class SingingWaiter : public Waiter, public Singer
{
    // TODO: do something
};
2.3.2.1 虚基类的疑问

看到上面的虚基类使用方式定义,虚基类的声明及虚基类的使用后,可能会产生如下几个疑问?

疑问1: 为什么使用术语“虚”?

疑问2: 为什么不抛弃将基类声明为虚的这种方式,而使虚行为成为多MI(多重继承)的准则呢?

疑问3: 是否存在麻烦呢?

下面根据上述的3个疑问,再来深入分析多重继承下的虚基类存在的意义:

疑问1解析:

  1. 在虚函数与虚基类之间并不存在明显的联系。C++用户强烈反对引入新的关键字,因为这样会给C++用户带来很大的使用压力。
  2. 例如,如果新关键字与重要程序中的重要函数或变量名称相同,这将非常麻烦。
  3. 因此,C++对这种新特性也使用关键字virtual – 有点像关键字重载

疑问2解析:

  1. 在一些情况下,可能需要基类的多个拷贝
  2. 将基类作为虚的要求程序完成额外的计算,为不需要的工具付出代价是不应当的
  3. 这样做也有其缺点,后续再补充说明

疑问3解析:

  1. 是的,是存在麻烦的。
  2. 为使虚基类能够工作,需要对c++规则进行调整,必须以不同的方式编写一些代码
  3. 使用虚基类还可能需要修改已有的代码。例如,将SingingWaiter类添加到Worker集成层次中时,需要在Singer类和Waiter类中添加关键字virtual。
2.3.3 虚基类规则下的构造函数新规则
  1. 使用虚基类时,需要对类构造函数采用一种新的方法。
  2. 对于非虚基类,唯一可以出现在初始化列表中的构造函数是即时基类构造函数。但这些构造函数可能需要将信息传递给其基类。如下示例代码:
// 基类A
class A
{
    int a;
public:
    A(int n = 0) : a(n) { }
    // TODO: do something
    // ...
};

// 派生类B
class B : public A
{
    int b;
public:
    B(int m = 0, int n = 0) : A(n), b(m) { }
    // TODO: do something
    // ...
};

// 派生类C
class C : public B
{
    int c;
public:
    C(int q = 0, int m = 0, int n = 0) : B(m, n), c(q) { }
    // TODO: do something
    // ...
};
2.3.3.1 解释说明

使用非虚基类时: 上述示例代码解释说明,以便更能方便的理解使用非虚基类时的派生类信息传递给基类的传递过程,分析如下:

  • 派生类C的构造函数只能调用基类B类的构造函数,派生类C的构造函数使用值q,并将值m和n传递给基类B的构造函数
  • 派生类B的构造函数只能调用基类A的构造函数,派生类B的构造函数使用值m,并将值n传递给基类A的构造函数

使用虚基类时: 则上述的信息自动传递将不起作用。如下的MI(多重继承)示例构造函数,并且对这段示例构造函数的说明放在示例代码之后,以便深入理解:

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

问题1:使用虚基类时,上述信息传递存在的问题

自动传递信息时,将通过2条不同的途径(Waiter和Singer)将wk传递给Worker对象。

问题1解决:

  • 为避免问题1的冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类。
  • 也就是上述的构造函数将会初始化p与v参数对应的成员变量,但wk参数中的信息将不会传递给子对象Waiter和Singer。
  • 然而,编译器必须在构造派生对象之前构造基类对象组件,在上述构造函数情况下,编译器将使用Worker的默认构造函数
  • 如果不希望使用默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数。因此,具体的构造函数如下示例代码所示:
// 1、本示例代码显式的调用构造函数Worker(const Worker&)
// 2、这种用法是合法的,对于虚基类必须这样做;对于非虚基类,则是非法的
SingingWaiter(const Worker& wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk, p), Singer(wk, v) {}

问题1解决方式下的注意点:

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

三、多重继承的其它规则表现

3.1 二义性

多重继承除了修改类构造函数规则外,MI(多重继承)通常还要求调整其它代码。

假设没有在SingingWaiter类中重新定义Show()方法,并试图使用SingingWaiter对象调用继承的Show()方法,如下使用SingingWaiter对象的示例代码:

SingingWaiter sw("Test L", 2345, 6, soprano);
sw.Show();

上述示例代码说明:

  1. 对于单继承,如果SingingWaiter类中没有重新定义Show(),则将使用最近祖先中的定义
  2. 在多重继承中,每个直接祖先都有一个Show()函数,这就使得下述调用是二义性

二义性问题解决方式:

  • 1、可以使用作用域解析运算符来说明使用的具体方法,如下示例代码:
SingingWaiter sw("Test L", 2345, 6, soprano);
// 使用的是Singer基类的Show()函数
sw.Singer::Show();
  • 2、更好的方式是: 在SingingWaiter类中重新定义Show()方法,并指出要使用哪个基类的Show()函数。假设希望SingingWaiter类重新定义的Show()使用Singer类的Show()函数,可以进行如下定义:
void SingingWaiter::Show()
{
    Singer::Show();
}
3.2 多重继承下的派生类调用基类方法

对于单继承来说,让派生方法调用基类的方法是可以的;但是在多重继承中,派生类方法调用多个基类的方法,就会出现基类的基类方法被调用多次的情况。

基类的基类被调用多次的情况如何解决呢? 方式如下:

  • 1、一种办法是使用模块化方式,而不是递增方式,示例代码如下:
// 1、不友好的方式:递增方式
void Worker::Show() const 
{
    cout << "Name: " << fullname << "\n";
    cout << "Employee ID: " << id << "\n";
}

void Waiter::Show() const
{
    Worker::Show();
    cout << "Panache rating: " << panache << "\n";
}

void Singer::Show() const
{
    Worker::Show();
    cout << "Vocal range: " << pv[voice] << "\n";
}

coid HeadWaiter::Show() const
{
    Waiter::Show();
    cout << "Presence rating: " << presence << "\n";
}

// 递增方式下,如果是这样调用就会出现调用Worker::Show()多次的情况
void SingingWaiter::Show() const
{
    Waiter::Show();
    Singer::Show();
}

// 2、友好的方式:模块化方式
void Worker::Data() const
{
    cout << "Name: " << fullname << "\n";
    cout << "Employee ID: " << id << "\n";
}

void Waiter::Data() const
{
    cout << "Panache rating: " << panache << "\n";
}

void Singer::Data() const
{
    cout << "Vocal range: " << pv[voice] << "\n";
}

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

// 1、模块化方式下,这样调用实现只调用一次Worker类对象的方法一次
// 2、Data()方法可只在类内部使用,作为协助公有接口的辅助方法。使Data()方法成为保护的,则只能在继承层次结构中的类中使用它,在其它地方不能使用
// 3、使Data()方法成为私有的将阻止Waiter中的代码使用Worker::Data(),这正是保护访问类的用武之地
void SingingWaiter::Show() const
{
 	Worker::Data();
    Data();
}
  • 2、第二种方法是将所有的数据组件都设置为保护的,而不是私有的,不过使用保护方法(而不是保护数据)将可以更严格的控制对数据的访问。
// 举例如下:set()方法取得数据,以设置对象值,该方法也有类似的问题。如SingingWaiter::Set()方法应请求Worker信息一次,而不是两次。
// 1、可以使用前面提到的模块化方式解决
// 2、可以提供一个受保护的get()方法,该方法只请求一个类的信息

四、多重继承多次改进最终形态

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

改进多次后的最终形态示例代码如下所示,记录在此,方便后续根据前面的说明进行代码比对理解:

// .h头文件
// 说明:
// 1、该示例代码使用了多态属性,将各种类的地址赋给基类指针。
class WorkerMI
{
private:
    std::string fullname;
    long id;

protected:
    virtual void data() const;
    virtual void get();

public:
    WorkerMI();
    WorkerMI(const std::string& s, long n);

    virtual ~WorkerMI() = 0;
    virtual void set() = 0;
    virtual void show() const = 0;
};

class WaiterMI : virtual public WorkerMI
{
private:
    int panache;

protected:
    void data() const;
    void get();

public:
    WaiterMI();
    WaiterMI(const std::string& s, long n, int p = 0);
    WaiterMI(const WorkerMI& wk, int p = 0);

    void set();
    void show() const;
};

class SingerMI : virtual public WorkerMI
{
protected:
    enum
    {
        otherMI,
        altoMI,
        contraltoMI,
        sopranoMI,
        bassMI,
        baritoneMI,
        tenorMI,
    };

    enum
    {
        VtypesMI = 7,
    };

    void data() const;
    void get();

private:
    static char* pvmi[VtypesMI];
    int voice;

public:
    SingerMI();
    SingerMI(const std::string& s, long n, int v = otherMI);
    SingerMI(const WorkerMI& wk, int v = otherMI);

    void set();
    void show() const;
};

class SingingWaiterMI : public SingerMI, public WaiterMI
{
protected:
    void data() const;
    void get();

public:
    SingingWaiterMI();
    SingingWaiterMI(const std::string& s, long n, int p = 0, int v = otherMI);
    SingingWaiterMI(const WorkerMI& wk, int p = 0, int v = otherMI);
    SingingWaiterMI(const WaiterMI& wk, int v = otherMI);
    SingingWaiterMI(const SingerMI& wk, int p = 0);
    virtual ~SingingWaiterMI();

    void set();
    void show() const;
};
// .cpp源文件
WorkerMI::WorkerMI()
    : fullname("no one")
    , id(0L)
{

}

WorkerMI::WorkerMI(const std::string &s, long n)
    : fullname(s)
    , id(n)
{

}

WorkerMI::~WorkerMI()
{

}

void WorkerMI::data() const
{
    std::cout << "Name: " << fullname << std::endl;
    std::cout << "Employee ID: " << id << std::endl;
}

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

// WaiterMI
WaiterMI::WaiterMI()
    : WorkerMI()
    , panache(0)
{

}

WaiterMI::WaiterMI(const std::string &s, long n, int p)
    : WorkerMI(s, n)
    , panache(p)
{

}

WaiterMI::WaiterMI(const WorkerMI &wk, int p)
    : WorkerMI(wk)
    , panache(p)
{

}

void WaiterMI::data() const
{
    std::cout << "Panache rating: " << panache << std::endl;
}

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

void WaiterMI::set()
{
    std::cout << "Enter waiter's name: ";
    WorkerMI::get();
    get();
}

void WaiterMI::show() const
{
    std::cout << "Category: waiter\n";
    WorkerMI::data();
    data();
}

// SingerMI
// 派生类Singer
char* SingerMI::pvmi[VtypesMI] = {"other", "aito", "contralto", "soprano", "bass", "baritone", "tenor"};

SingerMI::SingerMI()
    : WorkerMI()
    , voice(otherMI)
{

}

SingerMI::SingerMI(const std::string &s, long n, int v)
    : WorkerMI(s, n)
    , voice(v)
{

}

SingerMI::SingerMI(const WorkerMI &wk, int v)
    : WorkerMI(wk)
    , voice(v)
{

}

void SingerMI::data() const
{
    std::cout << "Vocal range: " << pvmi[voice] << std::endl;
}

void SingerMI::get()
{
    std::cout << "Enter number for singer's vocal range: ";
    int i;
    for (i = 0;i < VtypesMI; ++i) {
        std::cout << i << ": " << pvmi[i] << "  ";
        if (i % 4 == 3) {
            std::cout << std::endl;
        }
    }

    if (i % 4 != 0) {
        std::cout << std::endl;
    }

    std::cin >> voice;
    while (std::cin.get() != '\n') {
        continue;
    }
}

void SingerMI::set()
{
    std::cout << "Enter singer's name: ";
    WorkerMI::get();
    get();
}

void SingerMI::show() const
{
    std::cout << "Category: singer\n";
    WorkerMI::data();
    data();
}

// SingingWaiterMI
SingingWaiterMI::SingingWaiterMI()
{

}

SingingWaiterMI::SingingWaiterMI(const std::string &s, long n, int p, int v)
    : WorkerMI(s, n)
    , WaiterMI(s, n, p)
    , SingerMI(s, n, v)
{

}

SingingWaiterMI::SingingWaiterMI(const WorkerMI &wk, int p, int v)
    : WorkerMI(wk)
    , WaiterMI(wk, p)
    , SingerMI(wk, v)
{

}

SingingWaiterMI::SingingWaiterMI(const WaiterMI &wt, int v)
    : WorkerMI(wt)
    , WaiterMI(wt)
    , SingerMI(wt, v)
{

}

SingingWaiterMI::SingingWaiterMI(const SingerMI &sg, int p)
    : WorkerMI(sg)
    , WaiterMI(sg, p)
    , SingerMI(sg)
{

}

SingingWaiterMI::~SingingWaiterMI()
{

}

void SingingWaiterMI::data() const
{
    SingerMI::data();
    WaiterMI::data();
}

void SingingWaiterMI::get()
{
    WaiterMI::get();
    SingerMI::get();
}

void SingingWaiterMI::set()
{
    std::cout << "Enter singer waiter's name: ";
    WorkerMI::get();
    get();
}

void SingingWaiterMI::show() const
{
    std::cout << "Category: singing waiter\n";
    WorkerMI::data();
    data();
}

五、有关MI(多重继承)的其它问题

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

通过多种途径继承一个基类的派生类情况:

  • 1、如果基类是虚基类,派生类将包含基类的一个子对象
  • 2、如果基类不是虚基类,派生类将包含多个子对象。
  • 3、如果虚基类和非虚基类混合时,当如何?当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象,举例如下:
// 举例说明虚基类和非虚基类混合时的继承情况
// 1、假设B类时C类和D类的虚基类
// 2、假设B类同时是X类和Y类的非虚基类
// 3、类M是从C、D、X和Y派生而来的
// 4、这种情况下类M从虚派生祖先(C类和D类)那继承了一个B类子对象
// 5、这种情况下类M从非虚派生祖先(即X类和Y类)分别继承了一个B类子对象,即2个B类子对象
5.2 虚基类和支配

如果类从不同的类那里继承了两个或更多的同名成员(数据或方法),则使用该成员名时,如果没有用类名进行限定,将导致二义性。

但如果使用的是虚基类,则这样做不一定会导致二义性。在这种情况下,如果某个名称优先于其它所有名称,则使用它时,即使不使用限定符,也不会导致二义性。

问题1: 一个成员名如何优先于另一个成员名呢?

解决方式1: 派生类中的名称优先于直接或间接祖先类中的相同名称。如下示例代码:

class B
{
public:
	short ();
    // TODO: do something
    // ...
};

// 1、类C中的q()函数定义优先于类B中的q()定义,因为类C是从类B派生而来的。
// 2、因此,类F中的方法可以使用q()来表示C::q()
class C : virtual public B
{
public:
    long q();
    ing omg();
    // TODO: do something
    // ...
};

class D : public C
{
	// TODO: do something
    // ...  
};

class E : virtual public B
{
private:
	int omg();
    // TODO: do something
    // ...
};

// 3、任何一个omg()定义都不优先于其他omg()定义,因为C和E都不是对方的基类。在F中使用非限定的omg()将导致二义性
// 4、虚二义性规则与访问规则无关,也就是说,即使E::omg()是私有的,不能在F类中直接访问,但是使用omg()仍将导致二义性
// 5即使C::q()是私有的,它将优先于D::q()。这种情况,可以在类F中调用B::q(),如果不限定q(),则将意味着要调用不可访问的C::q()
class F : public D, public E
{
    // TODO: do something
    // ...
};

六、MI(多重继承)总结

1、不使用虚基类

(1) 不使用虚基类的MI(多重继承)不会引入新的规则。

(2) 如果一个类从两个不同的类那里继承了两个同名的成员,则需要在派生类中使用类限定符来区分它们。

(3) 举例,从GunSlinger和PokerPlayer派生而来的BadDude类中,将分别使用GunSlinger::draw()和PokerPlayer::draw()来区分从这两个类那里继承的draw()方法。否则,编译器将指出二义性

2、使用虚基类

(1) 如果一个类通过多种途径继承了一个非虚基类,则该类从每种途径分别继承非虚基类的一个实例。通常情况下,多个基类实例都是问题。

(2) 下面是一个使用虚基类的MI(多重继承)。当派生类使用关键字virtual来指示派生时,基类就成为虚基类

class marketing : public virtual reality 
{
    // TODO: do something
} 

(3) 主要变化(同时也是使用虚基类的原因)是,从虚基类的一个或多个实例派生而来的类将只继承了一个基类对象。为实现这种特性,必须满足其它要求:

  • 1、 有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的。
  • 2、 通过优先规则解决名称二义性。

MI(多重继承)会增加编程的复杂度。然而,这种复杂性主要是由于派生类通过多条途径继承同一个基类引起的

你可能感兴趣的:(#,C++,Primer,Plus,c++,开发语言)