%%
# 纲要
> 主干纲要、Hint/线索/路标
1. "继承" 是如何实现的?
2. "派生类与基类"之前的关系,
- 派生类继承了基类的哪些部分?
- 派生类需要完成什么任务?——初始化基类的部分?
- 相互转换?
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
---
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 继承
C++中的**继承**(inheritance)是面向对象编程(OOP)的一个核心概念,允许**创建基于现有类的新类**。
"继承"机制提供了代码重用的手段,并能建立起类之间的层次关系。
继承的层级表现为:
- **基类**(base class):提供通用的属性和方法。
- **派生类**(derived class):可添加新的属性和方法,也可以重写继承来的方法。
派生类通过 "**==类派生列表==**" (class derivation list)来明确指出其从哪个**基类**继承而来,可以继承自**多个基类**。
每个派生类都会继承其 "==**直接基类**==(派生列表中指定的)" 的所有成员,因此也**包括了其==所有 "间接基类"== 的部分**。
一个派生类同时也可以作为其他派生类的 "基类"。
<br><br>
# 派生访问说明符
在 "类派生列表" 中需要指明 "**继承方式**",C++支持**三种类型的继承**,由 "派生访问说明符"指定。
"**派生访问说明符**" 控制的是 **基类的 "==公有成员==" 与 "==受保护成员=="** 在**派生类中**所呈现的**对外的 "访问权限"**:
- `public`:公有继承
- **基类的 `public` 与 `protected` 成员** 在派生类中**仍然分别为 `public`、`protected`**,保持不变;
- `protected`:受保护的继承
- **基类的 `public` 与 `protected` 成员** 在派生类中==**全变为 "`protected`"**==。
- `private`:私有继承
- **基类的 `public` 与 `protected` 成员**在派生类中==**全变为 "`private`"**==
总结:
| 继承方式 | 基类的 `public` 成员在派生类中 | 基类的 `protected` 成员在派生类中 |
| -------------- | -------------------- | ----------------------- |
| `public` 继承 | `public` | `protected` |
| `protected` 继承 | `protected` | `protected` |
| `private` 继承 | `private` | `private` |
> [!example] 说明示例:
>
> ```cpp title:derived_ways.cpp
> class Base {
> public:
> int publicMember;
> protected:
> int protectedMember;
> private:
> int privateMember;
> };
>
> class PublicDerived : public Base {
> // publicMember 仍是公有的
> // protectedMember 仍是保护的
> // privateMember 在 Derived 中不可访问
> };
>
> class ProtectedDerived : protected Base {
> // publicMember 和 protectedMember 在 Derived 中都变成受保护的
> // privateMember 在 Derived 中不可访问
> };
>
> class PrivatedDerived: private Base {
> // publicMember 和 protectedMember 在 Derived 中都变成私有的
> // privateMember 在 Derived 中不可访问
> };
> ```
>
> [!caution] 无论哪种派生方式,**==基类的 private 成员==均无法在派生类内 "直接访问"**
>
> - 派生类**从基类继承来的 `public` 和 `protected` 成员**能够在**派生类的成员函数(及友元)** 中直接访问。
> - 派生类**从基类继承来的 `private` 成员**在派生类中**不可直接访问**,派生类只能通过其继承的 "**基类的成员函数**" 来修改 **基类的 `private` 成员**。
>
> [!caution]
> 派生类只对**其自身 "==从基类继承来的、基类部分 public 和 protected 成员== "具有直接访问权限**,
> 但是**不能直接通过 "基类" 或 "基类对象" 去访问基类的 protected 成员**。
>
> ```cpp
> class Base {
> protected:
> int prot_mem;
> };
>
> class Derived : public Base {
> public:
> friend void visit (Derived&); // 可以访问 Derived::prot_mem, 这是派生类继承来的、其自身中的基类部分的成员.
> friend void visit (Base&); // 不能访问 Base::prot_mem, 这不属于派生类本身, 而是"基类".
> };
> ```
>
>
> [!NOTE] `class` 的默认继承方式为 `private`,而 `struct` 默认继承方式为 `public`
>
> ```cpp
> class Base {};
> class D1: Base {}; // 默认为`private`继承
> struct D2: Base {}; // 默认为`public`继承
> ```
>
>
<br><br>
# 派生类的作用域
**派生类的作用域** 嵌套在 **==基类的作用域==** 之内。
因此,如果一个名字在**派生类的作用域**内无法正确解析,则编译器将继续**在外层的基类作用域**中寻找该名字的定义。
**派生类可以 ==重用== 定义在其直接基类或间接基类,以及更外层作用域中的名字**,
此时**定义在==派生类内层作用域中的名字==将隐藏定义在==外层作用域的名字==**。可使用**作用域运算符`::`** 明确地**使用外层作用域中的名称**。
<br><br>
# 基类的静态成员
基类中的静态成员始终**只存在一个实体**,无论从基类中派生出多少个派生类。
如果基类的静态成员是**非 `private` 的**,则也可以**通过派生类来访问**。
```cpp
struct Base {
static void stkmem();
}
struct Derived : public Base {
//...
}
Base::stkmem();
Derived::stkmem(); // 派生类调用基类的静态成员函数
```
<br><br>
# 派生类与基类之间的"初始化构造"以及"拷贝控制"关系
> 参见 [^3]
当创建**派生类对象**时,首先会调用其 **==直接基类==及==虚基类==的构造函数**来初始化**派生类对象中的基类以及部分**。
同样,对于派生类的**拷贝控制操作**,**除析构函数以外**,都需要**对其基类部分显式地进行处理**:
- 在派生类的**拷贝/移动构造函数**中,应在 **==成员初始化列表==** 中显式调用**基类的拷贝/移动构造函数**来初始化对象的基类部分;
- 在派生类的**拷贝/移动赋值操作**中,应通过 **==类作用域运算符==** 显式调用**基类的拷贝/移动赋值运算符**来正确地赋值基类部分。
- 在派生类的**析构函数**中,**只需要销毁派生类自己分配的资源**,**而==基类部分的析构函数==将被==自动隐式调用**==。
对于一个派生类,其编译器自动提供的 "**合成版本(Synthesized)**"的**拷贝控制操作**,都会**自动调用直接基类中的对应操作**。
> [!caution] 继承体系下,编译器为派生类提供 "合成版本" 拷贝控制操作的规则 [^2]
>
>
> - 若**基类的==默认构造函数==、==拷贝控制操作== 是`private` 或 `=delete`**,则**派生类中的对应成员将是 `= deleted`**。
> - 因为**编译器无法使用==基类成员==来对==派生类对象中的基类部分==进行处理**。
>
> - 若**基类的析构函数是`private` 或 `=delete`**,则派生类中 **"合成"的默认构造函数与拷贝、移动构造函数将是 `= deleted`**;
> - 因为**无法对基类部分进行销毁,也就不能创建基类部分**。
>
> - 若**基类的析构函数是虚函数**(虚函数即意味着必须定义,例如声明 `=default`),则**编译器不会为其生成合成的移动操作**,
> - 故**编译器也不能为派生类合成移动操作**。
派生类继承示例:
```cpp title:inheritance.cpp
class Base {
public:
Base() = default;
Base(int value, char ch) : baseValue(value), baseChar(ch) {
// 基类的构造函数
}
virtual ~Base() = default;
protected:
int baseValue;
char baseChar;
};
class Derived: public Base {
public:
// 派生类的默认构造函数, 调用基类的默认构造函数
Derived() : Base() {};
// 派生类的构造函数, 在成员初始化列表中首先调用"基类的构造函数`Base(int, char)`"
Derived(int b_v, char b_ch, int derivedV) : Base(b_v, b_ch), derivedValue(derivedV) {}
// 派生类的拷贝构造函数, 在调基类的拷贝构造体
Derived(const Derived& other) : Base(other) {
derivedValue = other.derivedValue;
}
// 派生类的拷贝赋值运算符, 调用基类的拷贝赋值运算符
Derived& operator=(const Derived& other){
if (this != &other) {
Base::operator=(other); // 调用基类的拷贝赋值运算符
// ...派生类特有的拷贝逻辑
derivedValue = other.derivedValue;
}
return *this;
}
// 派生类的移动构造函数, 调用基类的移动构造函数
Derived(Derived&& other) noexcept : Base(std::move(other)) {
derivedValue = other.derivedValue;
}
// 派生类的移动赋值运算符, 调用基类的移动赋值运算符
Derived& operator=(Derived&& other) noexcept {
if (this != &other) {
Base::operator=(std::move(other)); // 调用基类的移动赋值运算符
// 派生类特有的移动赋值逻辑
// ...
derivedValue = other.derivedValue;
}
return *this;
}
private:
int derivedValue;
};
```
<br><br>
### 派生类中对基类部分的初始化
**派生类**的**成员初始化列表**中不能直接初始化基类的数据成员,**必须在其 "==成员初始化列表==" 中调用 ==基类的构造函数==来初始化其 "==基类部分=="**。
成员初始化列表中**对基类构造函数的调用必须是==第一个操作==**,确保**基类成员**最先被初始化。
> [!caution] 如果派生类的构造函数**未显式调用基类的构造函数**,则将**自动调用基类的==默认构造函数==**。
> 在该情况下,如果 **==基类没有无参的默认构造函数==(不存在、或属于 `private` 或被声明为 `=delete`)**,则会编译错误。
> [!note]
> **派生类应当==遵循基类的接口==**,通过**调用基类的构造函数来初始化从基类中继承而来的成员**。
> 即使从语法上可以在**派生类的构造函数体内**为其公有或受保护的基类成员赋值,也 **==不应当这么做==**。
<br>
#### 派生类中基类部分的构造与析构顺序
对一个派生类,其基类的构造顺序**与 "派生列表" 中基类的出现顺序保持一致**,而**与派生类构造函数的成员初始化列表中的调用顺序无关**。
具体来说,将根据派生类的 "派生列表" 中**各个==直接基类==** 的声明顺序:
- 首先**构造所有==虚基类==**:编译器将**依次检查每个==直接基类==是否含有虚基类**,最先完成所有虚基类的构造。
- 然后**根据声明顺序逐一构造==非虚的直接基类==**,每个直接基类再分别完成各自基类的构造。
**析构顺序**则与构造顺序**恰好相反**,最先被构造的基类会最后被销毁。
> [!NOTE]
> - 当**构造**一个派生类对象时,其**虚基类部分将==首先==被构造**,然后是其他非虚基类的部分,最后才会初始化派生类特有的成员;
> - 当**析构**一个派生类对象时,其**虚基类部分将==最后==被销毁**,首先销毁派生类特有部分,然后销毁非虚基类的部分。
>
<br><br>
### 使用派生类对象对基类对象进行"初始化或赋值"
C++中**可以使用==派生类对象==对一个==基类对象==初始化或赋值**,将会调用**基类的拷贝/移动构造函数**、拷贝/移动赋值运算符。
其中,**只有该派生类对象中的基类部分会被拷贝、移动或赋值**,而**派生类特有的部分将会被忽略掉**,即发生 **==对象切片==问题**(sliced down)。
```cpp title:use_derived_obj_init_or_assign_a_base_obj.cpp
struct Base {
Base() = default;
Base(const Base& other) {
cout << "Invoke Base's copy constructor\n";
}
Base(Base&& other) {
cout << "Invoke Base's move constructor\n";
}
Base& operator=(const Base& other) {
if (this != &other) {
cout << "Invoke Base's operator=()\n";
}
return *this;
}
};
struct Derived : public Base{
Derived() = default;
};
int main() {
Derived obj_d;
// 使用派生类来初始化基类对象.
Base obj_b1 = obj_d; // 调用基类的拷贝构造函数
Base obj_b2 = std::move(obj_d); // 调用基类的移动构造函数
// 使用派生类来为基类赋值.
obj_b1 = obj_d; // 调用基类的拷贝赋值运算符
obj_b1 = std::move(obj_d); // 调用基类的移动赋值运算符
}
```
<br><br>
# final 禁止继承与禁止重写
![[02-开发笔记/01-cpp/类与对象/cpp-类成员函数的基本说明#final 说明符|cpp-类的成员函数#final 说明符]]
<br><br>
# using 声明在继承中的三个作用
### 使用 `using` 声明修改派生访问权限
派生类可通过 **`using` 声明**来修改**对 ==从基类继承来的某个可访问成员==的 "派生访问权限"** ,将其添加到**派生类的特定访问权限域中**。
**`using` 声明语句中的==名字/标识符的访问权限==**,由**该 `using` 声明语句之前的==访问说明符**==来决定。
```cpp title:change_derived_access
class Base {
public:
int public_mem = 5;
protected:
void prot_method() {};
};
class Derived : private Base { // 私有继承, 派生类从基类继承来的所有基类部分变成 "派生类的私有成员".
public:
// 派生类在其"public"访问说明符下通过`using`语句将继承来的"private成员"变为 "public成员"
using Base::public_mem;
using Base::prot_method;
};
int main() {
Derived obj;
obj.public_mem;
obj.prot_method();
}
```
### 使用 `using` 声明引入基类中被隐藏的同名函数
> 参见 [^1]
派生类中的函数会 **"==隐藏==" 基类中的所有同名函数**,而不构成重载。
如果希望**将基类中的同名函数引入,与派生类定义的版本同时形成重载**,可使用 `using` 声明引入,如下所示:
```cpp
struct Base {
void memfcn();
virtual void memfcn(int);
};
struct Derived : Base {
void memfcn(string); // 将 "隐藏"基类的所有memfcn, 而不是构成重载.
using Base::memfcn; // 通过`using`声明, 将原先基类中被隐藏的同名函数全部引入
// 此时, 派生类中存在着三个重载版本.
};
int main() {
Derived d;
d.memfcn("hello");
d.memfcn(); // error: 基类的`memfcn()`已经被隐藏, 而不构成重载
d.memfcn(10); // error: 基类的`memfcn(int)`已经被隐藏, 而不构成重载
}
```
### 使用 `using` 声明继承构造函数
> 这是 `using` 声明用于 "**基类构造函数**" 时的特殊作用,相当于**语法糖**,旨在简化派生类构造函数的编写。
派生类可以通过 `using` 声明 **重用/继承** 其 **==直接基类==** 定义的构造函数[^4]。
"**==重用/继承==**" 是指对于**基类的每个构造函数**(除默认、拷贝、移动构造函数外),
编译器都将**在派生类中生成一个与之对应的、==形参列表完全相同的构造函数**==,形式为 `Derived(params) : Base(args) {};` 。
其中,派生类自己的数据成员将被 "**默认初始化**"。
由此,**可以直接初始化派生类对象**,而无需在派生类中显式地定义这些"派生类的构造函数"。
> [!caution] `using` 声明用于继承 **==构造函数==**的注意事项
> - ** `using` 声明不会继承基类的==默认、拷贝、移动构造函数==**。 <br>如果派生类没有自定义,则编译器将可能根据对应情况自动提供 "合成版本"。
> >
> - **`using` 声明不会继承基类中 "==与派生类已有构造函数 '同签名'== 的基类构造函数"**
> >
> - **`using` 声明不会改变继承的基类构造函数的==访问级别==**,无论 `using` 声明语句位于派生类定义中的哪个访问说明符下。(这与 `using` 声明用于其他名称时不同)
> >
> - **`using` 声明不会继承基类的构造函数中的"==默认实参=="**。<br>对于**一个具有默认实参基类构造函数**,派生类将继承得到**多个**无默认实参的版本:
> - 一个版本包含完整形参列表,但都没有默认实参;
> - 其余多个版本,每个版本**分别去除掉一个 "原本具有默认实参" 的形参**。
> >
> - 在涉及**多重继承**时,如果派生类的**多个直接基类**中**存在 '==同签名==' 的构造函数**,则派生类必须为该构造函数定义其自己版本而不能继承,否则编译错误。
```cpp title:derive_constructor_of_base_class.cpp
class Base {
public:
Base(int x) {
cout << "Invoke `Base(int)` " << endl;
}
Base(char ch) {
cout << "Invoke `Base(char)` " << endl;
}
Base(double db) {
cout << "Invoke `Base(double)` " << endl;
}
};
class Derived : public Base {
public:
// 通过`using`声明继承基类的构造函数(除默认、拷贝、移动构造函数)
using Base::Base;
};
int main() {
// Derived自己未定义构造函数, 而是通过`using`声明重用/继承了基类的构造函数.
Derived d1(10); // 相当于调用`Derived(int) : Base(int)`;
Derived d2(3.16); // 相当于调用`Derived(double) : Base(double)`;
Derived d3('F'); // 相当于调用`Derived(char) : Base(char)`;
}
```
<br><br><br>
# 派生类与基类的类型转换
![[02-开发笔记/01-cpp/类型相关/cpp-类型转换#向上转型与向下转型|cpp-类型转换#向上转型与向下转型]]
<br><br><br>
# 虚继承
"**虚继承"**(virtual derived)机制用于解决**多重继承**时,**一个派生类==通过不同路径间接地多次继承同一个基类==导致派生类中==存在多个基类实例副本**== 的问题[^5]。
**虚继承**通过在 "派生列表" 中使用 **`virtual` 关键字** 指定,被 "**虚继承**" 的基类称为 "**==虚基类==**(virtual base class)**"。
> [!NOTE] **虚继承**不影响该**派生类**本身,只影响 "**其派生类进一步派生出来的类,即==间接子类==**"。
>
> "虚继承" 意味着 **"该派生类的派生类" 将可以 "==共享" 该虚基类==,即只持有==虚基类的一份实例副本==**。
> [!example] 虚继承示例
> 对于下列继承关系中,通过**对 `Derived1` 和 `Derived2` 声明为"虚继承"自 `Base`**,则当另一个派生类同时继承自 `Derived1` 和 `Derived2` 时,其**间接基类 `Base` 的部分将只存在一份示例**。
>
> ![[_attachment/02-开发笔记/01-cpp/类与对象/cpp-继承.assets/IMG-cpp-继承-FBFFC63BC18B3024691B54F9C1A39ACD.png|153]]
>
> 实现示例:
>
> ```cpp title: virtual_inherite. cpp
> class Base {
> public:
> // Base 类成员
> };
>
> class Derived1 : virtual public Base {}; // 声明虚继承
> class Derived2 : virtual public Base {}; // 声明虚继承
>
> // `Derived`中将只持有一份间接基类`Base`的实例。
> class Derived : public Derived1, public Derived2 {};
> ```
>
>
<br>
## 虚基类的成员
虚继承中,被共享的**虚基类部分只存在==唯一的实例==**(`Base::xxx`)。
如果**虚基类的成员**未被任何派生类成员**覆盖或隐藏**,则最底层派生类中**将可以==直接访问==虚基类成员**。
> [!example]
> ![[_attachment/02-开发笔记/01-cpp/类与对象/cpp-继承.assets/IMG-cpp-继承-13F6002369EA15912BA8C2AF304EE18E.png|905]]
说明示例:
- 对 VIM 而言,同时存在变量 `Derived1::ival` 以及 `Base::ival`,**后者被前者隐藏**。
- 对 VIM 而言,同时存在函数 `Derived1::bar(char)` 以及 `Base::bar(int)`,**后者被前者隐藏**.。
```cpp
struct Base {
void bar(int) { cout << "Base::bar(int)" << endl; }
int ival = 1;
double dval = 3.14;
};
struct Derived1 : virtual public Base {
void bar(char) { cout << "Derived1::bar(char)" << endl; }
void foo(char) { cout << "Derived1::foo(char)" << endl; }
char cval = 'A';
};
struct Derived2 : virtual public Base {
void foo(char) { cout << "Derived2::foo(char)" << endl; }
int ival = 2;
char cval = 'B';
};
struct VIM : public Derived1, public Derived2 {
void test() {
// foo('G'); // 二义性错误, 在其直接基类Derived1与Derived2中均存在
Derived1::foo('G');
// cout << cval << endl; // 二义性错误
cout << Derived1::cval << endl;
// 尽管Derived2中也有继承自基类的`Base::bar(int)`, 但被VIM的直接基类中的`Derived1::bar(char)`隐藏.
bar('G'); // Derived1::bar(char).
// 尽管Derived1中也有继承自基类的`Base::ival`, 但被VIM的直接基类中的`Derived2::ival`隐藏.
cout << ival << endl; // Derived2::ival
// 虚基类Base::dval, 未被其任何派生类同名成员覆盖或隐藏, 故在间接子类中可以被直接访问.
cout << dval << endl;
}
};
```
<br>
## 虚继承中的构造顺序
在虚继承中,由"**==最底层的"派生类==** 负责 **==构造虚基类的部分==**,**最底层派生类的构造函数**需要**调用==虚基类的构造函数==**。
> [!caution] 虚基类只能由最底层派生类初始化。对虚基类的初始化如果出现在 "中间基类"中,则这些初始值将被忽略。
- 编译器按照 "**直接基类**" 的**派生声明顺序**依次检查,如果其中存在 **直接基类之上存在==虚基类==**,则先**构造虚基类**。
- **所有虚基类**构造完成后,再按照声明顺序逐一构造 "**非虚基类**"。
> [!NOTE] 在普通继承中,**一个派生类只能初始化其 "==直接基类==",调用直接基类的构造函数**。
示例:
```cpp
class ZooAnimal {};
class Bear : virtual public ZooAnimal {};
class Character {};
class BookCharacter : public Character {};
class ToyAnimal {};
class TeddyBear : public BookCharacter,
public Bear,
public virtual ToyAnimal
{
public:
// 由最底层派生类初始化其"虚基类"
// 可显式调用其 "虚基类" 的构造函数, 若未显式调用则对其虚基类进行"默认初始化".
TeddyBear() : ToyAnimal(), ZooAnimal() {} // 虚基类构造顺序与此处调用顺序无关, 只取决于派生声明列表.
};
int main() {
TeddyBear obj;
// 构造顺序为: 首先构造完所有虚基类, 然后才是非虚基类.
// ZooAnimal();
// Toynimal();
// Character();
// BookCharacter();
// Bear();
// TeddyBear();
}
```
<br><br>
# 参考资料
# Footnotes
[^1]: 《C++ Primer》P551
[^2]: 《C++ Primer》P552
[^3]: 《C++ Primer》P552-556
[^4]: 《C++ Primer》P557
[^5]: 《C++ Primer》P718