Skip to content

Commit e2ccb7e

Browse files
committed
Updated to Chapter 10, Section 4
1 parent 74f0fe1 commit e2ccb7e

16 files changed

+358
-61
lines changed

generalized_parts/10_common_problems_in_inheritance.tex

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ \chapter{继承中的常见问题}
88
\item 棱形继承关系中,基类的成员重复了怎么办?\par
99
\end{itemize}
1010
这些问题在实际编程中很常见,所以我们需要好好研究一下,以防将来真的用的时候你突然一拍脑门说:``哎呀,我没学过这玩意。''那就有点麻烦了。\par
11-
本章的内容量非常大。硬度和第八章相仿,也请读者做好心理准备。\par
1211
\import{10_common_problems_in_inheritance/}{01_type_cast_in_inheritance_relationship.tex}
1312
\import{10_common_problems_in_inheritance/}{02_virtual_function_and_polymorphism.tex}
1413
\import{10_common_problems_in_inheritance/}{03_abstract_base_class_and_pure_virtual_function.tex}
15-
\import{10_common_problems_in_inheritance/}{04_mutiple_inheritance.tex}
14+
\import{10_common_problems_in_inheritance/}{04_multiple_inheritance.tex}
1615
\import{10_common_problems_in_inheritance/}{05_virtual_inheritance.tex}

generalized_parts/10_common_problems_in_inheritance/03_abstract_base_class_and_pure_virtual_function.tex

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ \section{抽象基类与纯虚函数}
1414
double _i; //专属于Complex类的虚部
1515
}
1616
\end{lstlisting}
17-
这样能解决代码上的困难,但是会造成逻辑上的困惑——复数怎么成了实数的一部分了?\par
17+
这样能解决代码上的困难,但是会造成逻辑上的困惑——复数怎么成了实数的一部分了?读者不要认为这个种属关系只有象征意义就可以乱写,实际上它们的关系会牵扯到很多操作,比如说实数和复数都能取共轭,但只有实数能应用比较运算符。如果我们把复数写成实数的派生类,那么比较运算符这边就很难处理。\par
1818
为了更妥善地解决这个问题,我们需要使用\textbf{抽象基类(Abstract base class, ABC)}。
1919
\subsection*{什么是抽象基类?}
2020
\textbf{抽象类(Abstract class)},简单说来就是不能定义对象的基类。我们曾通过把构造函数写在 \lstinline@protected@ 区或者 \lstinline@private@ 区的做法来防止在类(派生类)外定义对象,但这种做法仍不能避免我们在类内定义对象。抽象类则不同,我们不能在任何地方定义它的对象。与抽象类相对的叫作\textbf{具体类(Concrete class)},我们可以定义它们的对象。\par
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
\section{多重继承}
2+
在上一章中,我们看到了C++流输入/输出库中复杂的继承关系。其中的 \lstinline@std::basic_iostream@ 尤为引人注目。你没看错,这个类同时继承自 \lstinline@std::basic_ostream@ 和 \lstinline@std::basic_istream@。这种继承多个基类的操作叫做\textbf{多重继承(Multiple Inheritance)}。\par
3+
多重继承并不神秘。如果我们把继承视作是单纯的内嵌一个基类对象的话(私有继承是这样做的),那么多重继承就意味着在派生类对象中同时内嵌两个基类对象;如果我们把继承视作是共性成员的代码重用的话(公开继承是这样做的),那么多重继承就意味着派生类同时拥有这两个基类的特怔。\par
4+
\lstinline@std::stringstream@ 为例,它继承自 \lstinline@std::iostream@,而 \lstinline@std::iostream@ 多重继承自 \lstinline@std::istream@ 和 \lstinline@std::ostream@,所以它同时拥有 \lstinline@>>@ 和 \lstinline@<<@ 运算符的重载。于是这个类的对象既有输入功能又有输出功能。\par
5+
\begin{lstlisting}
6+
std::stringstream ss;
7+
std::string str;
8+
ss << "1024"; //向ss对象中输出1024
9+
ss >> str; //把ss对象中的内容输入到str中
10+
std::cout << str; //用std::cout把str中的内容输出到屏幕上
11+
\end{lstlisting}
12+
这段代码演示了字符串输入/输出功能。\lstinline@std::stringstream@ 对象可以从字符串中读取输出,或者把自己读取到的内容输入到字符串中\footnote{我们规定,信息从输入设备、内存等处传递给程序叫做输入;从程序传递给输出设备、内存等处叫做输出。详见第十三章。}。\par
13+
多重继承的语法也很简单,只要在继承列表中用逗号把若干基类隔开就行了。
14+
\begin{lstlisting}
15+
class A { /*...*/ };
16+
class B { /*...*/ };
17+
class Derived : public A, private B { /*...*/ }; 公开继承A,私有继承B
18+
\end{lstlisting}\par
19+
那么乍看上去,多重继承也没有什么特别的嘛,和单重继承差不多。其实不尽然,因为多重继承中有一些很麻烦的,我们不细想就想不到的问题。
20+
\subsection*{多重继承中的名称歧义问题}
21+
在单重继承时,是不会存在名称歧义的问题的。在单重继承时,如果我们在基类中定义了 \lstinline@id@ 而没有在派生类中定义 \lstinline@id@,那么使用派生类的成员 \lstinline@id@ 时,被使用的就是继承自基类的成员;如果我们在基类和派生类中都定义了 \lstinline@id@,那么使用用派生类的成员 \lstinline@id@ 时,被使用的就是派生类中定义的成员。如果我们非要使用基类的那个成员,就必须用显式地指定基类作用域,比如写成 \lstinline@Base::id@。\par
22+
总之,单重继承中,无论如何,程序总是能为一个名称绑定到适当的函数上;即便因为多态而必须进行迟绑定,也不会存在因为歧义而不知绑定到哪个函数上的问题。\par
23+
但在多重继承中,情况就变复杂了。我们完全可能遭遇这样的问题,两个基类中都有 \lstinline@id@ 这个名字,那么在进行多重继承时,这两个名字就会发生冲突。举个不太好的例子吧:我们要定义一个 \lstinline@Derived@ 类,它公开继承自 \lstinline@std::vector<int>@ 与 \lstinline@std::string@。
24+
\begin{lstlisting}
25+
class Derived : public std::vector<int>, public std::string {
26+
//公开继承自std::vector<int>,又公开继承自std::string
27+
Derived(const std::vector<int> &v, const std::string &s)
28+
: std::vector<int>{v}, std::string {s} {}
29+
};
30+
\end{lstlisting}
31+
接下来,当我们要调用 \lstinline@length@ 成员函数时,编译器自然知道我们是要调用继承自 \lstinline@std::string@ 的 \lstinline@length@,因为 \lstinline@std::vector<int>@ 中并没有这个名称。
32+
\begin{lstlisting}
33+
Derived d {{1,2,3,4},"str"};
34+
std::cout << d.length(); //编译器自然知道要调用的是什么;其返回值为3
35+
\end{lstlisting}
36+
但是当我们试图调用 \lstinline@size@ 成员函数时,问题出现了:
37+
\begin{lstlisting}
38+
std::cout << d.size();
39+
//error: request for member 'size' is ambiguous
40+
\end{lstlisting}
41+
出现这个问题的原因在于,\lstinline@Derived@ 从两个基类中各自继承了一个 \lstinline@size@ 成员函数。这两个成员函数之间没有覆盖关系,它们是完全并列的。在这种情况下,编译器就无法确定究竟要调用哪个 \lstinline@size()@ 函数。\par
42+
解决这个问题的方法很简单,我们只要通过作用域来显式地指定我们想要调用的函数就行了。
43+
\begin{lstlisting}
44+
std::cout << d.std::vector<int>::size(); //调用std::vector<int>的size成员
45+
\end{lstlisting}
46+
作用域解析运算符拥有最高优先级,所以 \lstinline@d.std::vector<int>::size()@ 的含义是 \lstinline@d.(std::vector<int>::size())@。也就是说,这里我们指定了调用的成员函数是 \lstinline@std::vector<int>::size()@。\par
47+
这样一来我们就解决了名称歧义的问题。\par
48+
\subsection*{初始化顺序问题}
49+
对于单重继承来说,初始化的顺序是一目了然的。对于每个派生类来说,都是先初始化它的基类部分,然后再初始化派生类的成员。如果它的基类又是另一个类的派生类,那么同样遵循这样的规则,先初始化这个基类的基类部分,再初始化这个基类的成员……以此类推。\par
50+
对于多重继承来说,初始化的顺序是按继承定义中的顺序进行的。
51+
\begin{lstlisting}
52+
struct A { A() { std::cout << "A::A()\n"; } };
53+
struct B { B() { std::cout << "B::B()\n"; } };
54+
struct C : A { C() { std::cout << "C::C()\n"; } };
55+
struct D : B { D() { std::cout << "D::D()\n"; } };
56+
struct E : C, D { E() : D{}, C{} { std::cout << "E::E()\n"; } };
57+
int main() {
58+
E{};
59+
}
60+
\end{lstlisting}
61+
这段代码中的继承关系很清晰,如图10.6所示。
62+
\begin{figure}[htbp]
63+
\centering
64+
\includegraphics[width=.36\textwidth]{../images/generalized_parts/10_multiple_inheritance_example_300.png}
65+
\caption{多重继承关系示例}
66+
\end{figure}
67+
那么它的运行结果将是怎样的呢?\\\noindent\rule{\linewidth}{.2pt}\texttt{
68+
A::A()\\
69+
C::C()\\
70+
B::B()\\
71+
D::D()\\
72+
E::E()
73+
}\\\noindent\rule{\linewidth}{.2pt}
74+
运行过程中总共有五个输出,我们可以把它分成三个过程:
75+
\begin{enumerate}
76+
\item 调用 \lstinline@C@ 的构造函数。在这一过程中,\lstinline@A::A()@ 先调用,然后是 \lstinline@C::C()@。虽然在 \lstinline@E::E()@ 当中我们把 \lstinline@D{}@ 写在了 \lstinline@C{}@ 前面,但是这并不会改变构造函数调用的顺序——这个顺序是取决于继承顺序的。
77+
\item 调用 \lstinline@D@ 的构造函数。在这一过程中,\lstinline@B::B()@ 先调用,然后是 \lstinline@D::D()@。
78+
\item 完成了 \lstinline@C::C()@ 和 \lstinline@D::D()@ 的调用之后,\lstinline@E::E()@ 调用。
79+
\end{enumerate}
80+
不要觉得多重继承中的初始化顺序很复杂,其实只是多了一项准则而已:按多重继承的基类顺序调用各个基类的构造函数——简单点说,从左到右。对于这个继承顺序来说,调用 \lstinline@E::E()@ 之前就应当先后调用 \lstinline@C::C()@ 和 \lstinline@D::D()@。至于这两个构造函数要干什么,那不是 \lstinline@E@ 应当管的事情。从运行结果上看,\lstinline@C::C()@ 在 \lstinline@D::D()@ 之前,\lstinline@D::D()@ 又在 \lstinline@E::E()@ 这前,这就够了。\par
81+
\subsection*{菱形继承结构的重复成员}
82+
我们说,派生类的对象相当于内嵌了一个基类的对象;或者说,派生类相当于拥有基类的成员\footnote{即便基类的私有成员对派生类不可见。}。多重继承会为我们带来另一个棘手的问题,就是在菱形的继承关系下,公共基类的对象,或者说成员,发生了重复。\par
83+
\begin{lstlisting}
84+
struct Base { int num {0}; }; //Base类中有num成员
85+
struct A : Base {}; //A类有继承自Base的num成员
86+
struct B : Base {}; //B类有继承自Base的num成员
87+
struct Derived : A, B {}; //Derived会继承A和B类的num成员各一个
88+
int main() {
89+
Derived de;
90+
std::cout << &de.A::num << std::endl << &de.B::num;
91+
//de的A::num成员和B::num成员是同一成员吗?地址值能告诉我们答案
92+
}
93+
\end{lstlisting}
94+
这段代码的运行结果是\\\noindent\rule{\linewidth}{.2pt}\texttt{
95+
0x7ffc1fc88548\\
96+
0x7ffc1fc8854c
97+
}\\\noindent\rule{\linewidth}{.2pt}
98+
换句话说,虽然 \lstinline@Derived@ 从根本上来说继承自 \lstinline@Base@,但是因为多重继承的缘故,它实际上有了两份 \lstinline@Base::num@ 成员,一个来自基类 \lstinline@A@,另一个来自基类 \lstinline@B@。\par
99+
\begin{figure}[htbp]
100+
\centering
101+
\includegraphics[width=.5\textwidth]{../images/generalized_parts/10_diamond_inheritance_300.png}
102+
\caption{菱形继承关系下,\lstinline@Derived@ 类内嵌了两份 \lstinline@Base@ 类的对象}
103+
\end{figure}
104+
在多数情况下,这并不是我们想要的结果,而且还会引发无数的麻烦——比如说,内存空间的浪费,我们明明只需要一份数据,但多重继承为我们搞出了两份数据;再比如说,名称的歧义和冲突,我们不能单纯写成 \lstinline@d.num@,必须写成 \lstinline@d.A::num@ 或 \lstinline@d.B::num@。它们本应该相同的。而在C++中,解决这个问题的对策,就是下一节中要讲到的虚继承。\par

generalized_parts/10_common_problems_in_inheritance/04_mutiple_inheritance.tex

-1
This file was deleted.
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<mxfile host="Electron" modified="2024-01-24T04:35:42.987Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.21 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="ae8k2rGsgHXRCn2XAyUU" version="22.1.21" type="device">
2+
<diagram name="Page-1" id="o9wBVmuPF-BfS5Mu2OIw">
3+
<mxGraphModel dx="666" dy="454" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
4+
<root>
5+
<mxCell id="0" />
6+
<mxCell id="1" parent="0" />
7+
<mxCell id="8g4pSloKbr1zPTXRMnoa-1" value="&lt;font face=&quot;consolas&quot;&gt;A&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1">
8+
<mxGeometry x="300" y="200" width="60" height="30" as="geometry" />
9+
</mxCell>
10+
<mxCell id="8g4pSloKbr1zPTXRMnoa-4" value="" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="8g4pSloKbr1zPTXRMnoa-2" target="8g4pSloKbr1zPTXRMnoa-1" edge="1">
11+
<mxGeometry relative="1" as="geometry" />
12+
</mxCell>
13+
<mxCell id="8g4pSloKbr1zPTXRMnoa-2" value="&lt;font face=&quot;consolas&quot;&gt;C&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1">
14+
<mxGeometry x="300" y="270" width="60" height="30" as="geometry" />
15+
</mxCell>
16+
<mxCell id="8g4pSloKbr1zPTXRMnoa-5" value="" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="8g4pSloKbr1zPTXRMnoa-3" target="k8vPMHwsldSHbkCenQTe-1" edge="1">
17+
<mxGeometry relative="1" as="geometry">
18+
<mxPoint x="480" y="270" as="targetPoint" />
19+
</mxGeometry>
20+
</mxCell>
21+
<mxCell id="8g4pSloKbr1zPTXRMnoa-3" value="&lt;font face=&quot;consolas&quot;&gt;D&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" parent="1" vertex="1">
22+
<mxGeometry x="420" y="270" width="60" height="30" as="geometry" />
23+
</mxCell>
24+
<mxCell id="k8vPMHwsldSHbkCenQTe-1" value="&lt;font face=&quot;consolas&quot;&gt;B&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" vertex="1" parent="1">
25+
<mxGeometry x="420" y="200" width="60" height="30" as="geometry" />
26+
</mxCell>
27+
<mxCell id="k8vPMHwsldSHbkCenQTe-3" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k8vPMHwsldSHbkCenQTe-2" target="8g4pSloKbr1zPTXRMnoa-2">
28+
<mxGeometry relative="1" as="geometry" />
29+
</mxCell>
30+
<mxCell id="k8vPMHwsldSHbkCenQTe-4" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k8vPMHwsldSHbkCenQTe-2" target="8g4pSloKbr1zPTXRMnoa-3">
31+
<mxGeometry relative="1" as="geometry" />
32+
</mxCell>
33+
<mxCell id="k8vPMHwsldSHbkCenQTe-2" value="&lt;font face=&quot;consolas&quot;&gt;E&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad9d5;strokeColor=#ae4132;" vertex="1" parent="1">
34+
<mxGeometry x="360" y="340" width="60" height="30" as="geometry" />
35+
</mxCell>
36+
</root>
37+
</mxGraphModel>
38+
</diagram>
39+
</mxfile>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<mxfile host="Electron" modified="2024-01-24T03:42:01.435Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.21 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="MrpUwvFk9DZban1v23O2" version="22.1.21" type="device">
2+
<diagram name="Page-1" id="o9wBVmuPF-BfS5Mu2OIw">
3+
<mxGraphModel dx="965" dy="659" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
4+
<root>
5+
<mxCell id="0" />
6+
<mxCell id="1" parent="0" />
7+
<mxCell id="8g4pSloKbr1zPTXRMnoa-1" value="&lt;font face=&quot;consolas&quot;&gt;抽象复数基类&lt;br&gt;_r&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EEEEEE;strokeColor=#36393D;" parent="1" vertex="1">
8+
<mxGeometry x="360" y="270" width="120" height="50" as="geometry" />
9+
</mxCell>
10+
<mxCell id="8g4pSloKbr1zPTXRMnoa-4" value="" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="8g4pSloKbr1zPTXRMnoa-2" target="8g4pSloKbr1zPTXRMnoa-1" edge="1">
11+
<mxGeometry relative="1" as="geometry" />
12+
</mxCell>
13+
<mxCell id="8g4pSloKbr1zPTXRMnoa-2" value="&lt;font face=&quot;consolas&quot;&gt;复数类&lt;br&gt;_r&lt;br&gt;_i&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1">
14+
<mxGeometry x="280" y="380" width="120" height="50" as="geometry" />
15+
</mxCell>
16+
<mxCell id="8g4pSloKbr1zPTXRMnoa-5" value="" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="8g4pSloKbr1zPTXRMnoa-3" target="8g4pSloKbr1zPTXRMnoa-1" edge="1">
17+
<mxGeometry relative="1" as="geometry" />
18+
</mxCell>
19+
<mxCell id="8g4pSloKbr1zPTXRMnoa-3" value="&lt;font face=&quot;consolas&quot;&gt;实数类&lt;br&gt;_r&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" parent="1" vertex="1">
20+
<mxGeometry x="440" y="380" width="120" height="50" as="geometry" />
21+
</mxCell>
22+
<mxCell id="8g4pSloKbr1zPTXRMnoa-8" value="&lt;font face=&quot;consolas&quot;&gt;复数类&lt;br&gt;_r&lt;br&gt;_i&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1">
23+
<mxGeometry y="270" width="120" height="50" as="geometry" />
24+
</mxCell>
25+
<mxCell id="8g4pSloKbr1zPTXRMnoa-11" value="" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="8g4pSloKbr1zPTXRMnoa-10" target="8g4pSloKbr1zPTXRMnoa-8" edge="1">
26+
<mxGeometry relative="1" as="geometry" />
27+
</mxCell>
28+
<mxCell id="8g4pSloKbr1zPTXRMnoa-10" value="&lt;font face=&quot;consolas&quot;&gt;实数类&lt;br&gt;_r&lt;br&gt;_i&lt;br&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" parent="1" vertex="1">
29+
<mxGeometry y="380" width="120" height="50" as="geometry" />
30+
</mxCell>
31+
<mxCell id="8g4pSloKbr1zPTXRMnoa-12" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;fillColor=#f5f5f5;gradientColor=#b3b3b3;gradientDirection=east;strokeColor=#666666;" parent="1" edge="1">
32+
<mxGeometry width="50" height="50" relative="1" as="geometry">
33+
<mxPoint x="140" y="350" as="sourcePoint" />
34+
<mxPoint x="260" y="350" as="targetPoint" />
35+
</mxGeometry>
36+
</mxCell>
37+
</root>
38+
</mxGraphModel>
39+
</diagram>
40+
</mxfile>

0 commit comments

Comments
 (0)