21. 类层次

多重继承

多重继承是一种面向对象编程的特性,它允许一个类从多个基类继承属性和方法。这种继承方式提供了两种主要好处:

  1. 共享接口(Shared Interface) :通过继承,可以减少重复代码,统一代码规范。这种方式通常被称为运行时多态(Run-time Polymorphism)或接口继承(Interface Inheritance)。
  2. 共享实现(Shared Implementation) :减少代码量,统一实现代码的规范,这种方式被称为实现继承(Implementation Inheritance)。

一个类可以同时采用这两种风格,以实现更灵活的代码复用和规范统一。

#include <iostream>
// 共享接口
class Animal {
public:
	virtual void speak() = 0;
};
// 共享实现
class Obj {
public:
	virtual void weight() {
		std::cout << "I am 10kg." << std::endl;
	}
};

class Dog : public Animal, protected Obj {
public:
	void speak() override {
		std::cout << "Woof!" << std::endl;
	}
	void weight() override {
		Obj::weight();
	}
};

int main() {
	Dog d;
	d.speak();
	d.weight();
}

多重继承时,如果对同一个成员函数有重复的定义,会报错。此时,可以通过类名区分。

class Animal {
public:
	void speak();
};

class Cat : public Animal {
};

class Dog : public Animal, public Cat {
};

int main() {
	Dog d;
	d.speak(); // Error: ambiguous call to speak
	d.Cat::speak(); // OK
	d.Animal::speak(); // OK
}

此外,还有一种解决办法就是在类中定义这个函数。

class Animal {
public:
	void speak();
};

class Cat : public Animal {
};

class Dog : public Animal, public Cat {
public:
	void speak();
};

int main() {
	Dog d;
	d.speak(); // OK
}

重复基类

在多重继承中,一个类可以有多个直接基类。这种设计可能导致一个基类在类层次结构中出现多次。

struct Storable {
    virtual string get_file() = 0;
    virtual void read() = 0;
    virtual void write() = 0;
    virtual ~Storable() {}
};

class Transmitter : public Storable {
public:
    void write() override;
    // ...
};

class Receiver : public Storable {
public:
    void write() override;
    // ...
};

class Radio : public Transmitter, public Receiver {
public:
    string get_file() override;
    void read() override;
    void write() override;
    // ...
};

可能出现的两种情况

  1. 两个 Storable子对象:一个 Radio​ 对象包含两个 Storable​ 子对象,一个是 Transmitter​ 的,另一个是 Receiver​ 的。
  2. 一个共享的 Storable子对象:一个 Radio​ 对象包含一个 Storable​ 子对象,这个对象由 Transmitter​ 和 Receiver​ 共享。

默认情况下,如果没有特殊说明,每个基类都会被拷贝一份。这意味着 Radio​ 类将有两个 Storable​ 的实例。如下图所示:

image

在派生类 Radio​ 中,可以覆盖 Storable​ 的虚函数,并且通常这个覆盖的函数会先调用其基类的版本,然后执行派生类自己的操作。

void Radio::write() {
    Transmitter::write();
    Receiver::write();
    // ...写入radio特定的信息...
}

这种设计允许 Radio​ 类在调用 write()​ 方法时,先执行 Transmitter​ 和 Receiver​ 的 write()​ 方法,然后添加 Radio​ 类特有的写入操作。

虚基类

为了避免 Storable​ 被重复使用,我们将其声明为虚基类。这样,无论 Radio​ 类通过多少个基类继承 Storable​,都只会有一个 Storable​ 实例被共享。

class Transmitter : public virtual Storable {
public:
    void write() override;
    // ...
};

class Receiver : public virtual Storable {
public:
    void write() override;
    // ...
};

class Radio : public Transmitter, public Receiver {
public:
    void write() override;
    // ...
};

可表示为下面图形:

image

同一层次体系中两个类共享数据的方法:

  1. 非局部作用域:将数据置于类的外部,如全局变量或命名空间变量。这种方法破坏了封装性和局部性,通常不推荐使用。
  2. 基类中放置数据:将数据放在基类中,这是最简单的做法。但在单继承中,这会导致所有派生类都能访问这些数据,类似于使用非局部数据,存在同样的问题。
  3. 指针共享数据:在某处分配对象,并将指针分别交给两个类。这种方法需要构造函数为共享对象分配内存、初始化,并提供指向共享对象的指针,这正是虚基类构造函数所做的。

虚基类提供了一个不是根的公共基类,允许在类层次中实现数据共享,避免了非局部数据的问题。如果不需要共享,可以不使用虚基类,代码会更简单。但如果需要共享,使用虚基类是更好的选择,否则需要自己构建变量来实现共享。

构造虚基类

虚基类在多重继承中用于确保基类的构造函数只被调用一次,即使在类层次结构中被多次提及。

虚基类永远被认为是其最终派生类的直接基类。

尽管多个基类可能都初始化同一个虚基类,编译器只会使用最终派生类提供的初始化器。

struct V { V(int i); }; // 虚基类V有一个接受int参数的构造函数
struct A { A(); }; // 基类A有一个默认构造函数

struct B : virtual V, virtual A {
    B() : V{ 1 } { /* ... */ } // 默认构造函数,必须初始化基类V
};

class C : virtual V {
public:
    C(int i) : V{ i } { /* ... */ } // 必须初始化基类V
};

class D : virtual public B, virtual public C {
    // 从B和C隐式地获取虚基类V
    // 从B隐式地获取虚基类A
public:
    D() { /* ... */ } // 错误:C和V没有默认构造函数
    D(int i) : C{ i } { /* ... */ } // 错误:V没有默认构造函数
    D(int i, int j) : V{ i }, C{ j } { /* ... */ } // OK
};

在这个例子中,D​ 类必须为虚基类 V​ 提供一个初始化器,即使 V​ 没有被显式声明为 D​ 的基类。这是因为虚基类被认为是最终派生类的直接基类,所以 D​ 必须负责其初始化。

重复基类与虚基类

在面向对象编程中,多重继承可以用来实现接口和提供实现细节。以下是两种不同的方法来处理接口和实现的关系,以及它们在设计上的影响和示例。

  1. 重复接口类:在类层次体系中每次提到接口时都创建一个对象

    class BB_ival_slider : public lval_slider, protected BBslider {
        // ...
    };
    
    class Popup_ival_slider : public lval_slider {
        // ...
    };
    
    class BB_popup_ival_slider : public Popup_ival_slider, protected BB_ival_slider {
        // ...
    };
    

    image

  2. 虚基类:层次体系中所有提及接口的类共享一个简单的对象

    class BB_ival_slider : public virtual lval_slider, protected BBslider {
        // ...
    };
    
    class Popup_ival_slider : public virtual lval_slider {
        // ...
    };
    
    class BB_popup_ival_slider : public virtual Popup_ival_slider, protected BB_ival_slider {
        // ...
    };
    

    image

覆盖虚基类函数

在面向对象编程中,派生类可以覆盖其直接或间接虚基类的虚函数。尤其是,两个不同的类可能会覆盖虚基类的不同的虚函数。通过这种方式,几个派生类就能共同为一个虚基类表示的接口提供实现了。

class Window {
public:
    virtual void set_color(Color) = 0; // 设置背景颜色
    virtual void prompt() = 0; // 用户交互
};

class Window_with_border : public virtual Window {
public:
    void set_color(Color) override; // 控制背景颜色
};

class Window_with_menu : public virtual Window {
public:
    void prompt() override; // 控制用户交互
};

class My_window : public Window_with_menu, public Window_with_border {
public:
    void prompt() override; // 改进用户交互
};

如果 My_window​ 类没有覆盖 prompt()​ 函数,而 Window_with_menu​ 和 Window_with_border​ 都覆盖了 prompt()​,则会导致错误,因为这样会造成二义性。为了避免这种情况,必须在最终派生类中提供一个覆盖函数,以确保虚函数表的一致性。

class Window {
public:
    virtual void set_color(Color) = 0; // 设置背景颜色
    virtual void prompt() = 0; // 用户交互
};

class Window_with_border : public virtual Window {
public:
    void prompt() override; // 控制背景颜色
};

class Window_with_menu : public virtual Window {
public:
    void prompt() override; // 控制背景颜色
};

class My_window1 : public Window_with_menu, public Window_with_border { // 正确:在最终派生类中提供一个覆盖函数
public:
    void prompt() override; // 控制背景颜色
};

class My_window2 : public Window_with_menu, public Window_with_border { // 报错:prompt 二义性
};

为虚基类提供部分实现(但非全部实现)的类称为混入类(mixin) ,如 Window_with_border ​和 Window_with_menu​。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇