14.3多重继承需要注意的问题

文章目录

    • 多重继承(MI)
      • 虚基类需要使用新的构造函数规则
      • MI每个直接祖先继承自祖宗(一般是一个抽象基类),拥有相同的方法接口(实现可能不同),这使得上述方法的调用是有二义性的。
        • 1. 使用作用域解析运算符
        • 2. 更好的方法是在孙子中使用 模块化方式(==配合保护访问方式==) **重新定义该方法**,并指出要使用哪个show()
      • 继承中的同名成员处理:

多重继承(MI)

  • 公有MI表示的是is-a关系
  • 私有MI和保护MI表示的是has-a关系

这里主要介绍公有MI

公有MI主要解决的几个问题:

  1. 从不同的基类继承多个相同的祖先对象成员------->使用虚继承使只继承一个祖先对象
  2. 从两个不同的基类继承同名方法-------->采用模块化方式而不是递增的方式编程,根据需要选择不同模块组合成新的方法。
  3. 从两个或更多相关基类那里继承同一个类的多个实例------->混合使用虚基类和非虚基类,会导致包含一个虚途经子对象和多个非虚途径子对象

虚基类需要使用新的构造函数规则


// multiple inheritance
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) {}
    // KP
    // 这里存在的问题是,在自动传递信息时,C++将通过两条不同的途径waiter和singer将wk信息传递给worker
    // 为了避免这种冲突,C++在基类是虚的时,将禁止信息通过中间类自动传递给基类
    // 这里 Waiter(s, n, p), Singer(s, n, v)将只能初始化中间类的新增成员p和v,而对基类成员不做处理
    // 这种情况下,需要自己额在成员初始化列表额外的显示调用所需的基类构造函数
    // 否则,这里默认将采用基类的默认构造函数
    // FAO 对于虚基类,这样做是合法且必须的;而对于非虚基类,这样则是非法的
    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;
}

MI每个直接祖先继承自祖宗(一般是一个抽象基类),拥有相同的方法接口(实现可能不同),这使得上述方法的调用是有二义性的。

解决方法:

1. 使用作用域解析运算符

2. 更好的方法是在孙子中使用 模块化方式(配合保护访问方式) 重新定义该方法,并指出要使用哪个show()

class Worker // an abstract base class
{
private:
    std::string fullname;
    long id;

protected:
    //KP 
    // 采用模块化方式而不是递增方式避免数据的重复调用
    // 这里用作协助公有接口的辅助方法
    // 采用保护方式避免使得这些方法只能在继承结构层次类使用他们
    virtual void Data() const; //worker数据的输入
    virtual void Get();    //worker数据的展示
    
public:
    //这里的两个方法调用了上面的保护辅助方法 
    virtual void Set() = 0;
    virtual void Show() const = 0;
};

class Waiter : virtual public Worker
{
private:
    int panache;

protected:
    void Data() const;
    void Get();

public:
// Waiter methods
// 这里使用Worker::Get()模块和派生类新增的Get模块来实现Set方法
void Waiter::Set()
{
    cout << "Enter waiter's name: ";
    // 输入worker部分
    Worker::Get();
    // 输入waiter增加部分
    Get();
}

void Waiter::Show() const
{
    cout << "Category: waiter\n";
    // 展示worker部分
    Worker::Data();
    // 展示waiter部分
    Data();
}
}

void Waiter::Show() const
{
    cout << "Category: waiter\n";
    // 展示worker部分
    Worker::Data();
    // 展示waiter部分
    Data();
}
};

class Singer : virtual public Worker
{
protected:
    void Data() const;
    void Get();
private:
    static char const *pv[Vtypes]; // string equivs of voice types
    int voice;
public:
    void Set();
    void Show() const;
};

// multiple inheritance
class SingingWaiter : public Singer, public Waiter
{
protected:
void SingingWaiter::Data() const
{
    Singer::Data();
    Waiter::Data();
}

void SingingWaiter::Get()
{
    Waiter::Get();
    Singer::Get();
}
public:
// 分别使用了worker模块和waiter新增的和singer新增的
// 模块化编程方式
void SingingWaiter::Set()
{
    cout << "Enter singing waiter's name: ";
    Worker::Get();
    // 模块化,包括了waiter和singer中各自新增的部分
    Get();
}
void SingingWaiter::Show() const
{
    cout << "Category: singing waiter\n";
    Worker::Data();
    Data();
}

};

继承中的同名成员处理:

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员,直接访问即可(通过优先规则解决名称二义性)
  • 访问父类同名成员,需要加作用域

总结:

  1. 子类对象可以直接访问子类中同名成员
  2. 子类对象加作用域可以访问到父类中同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名的成员函数,加作用域可以访问到父类中成员函数及其重载版本
  4. 非虚继承时,多个不同的类加类名限定即可。

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致,只不过有两种访问方式(通过对象 和 通过类名)

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域

你可能感兴趣的:(c++,primer,plus学习笔记,c++,开发语言)