📄 [21] inheritance proper inheritance and substitutability, c++ faq lite.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
<!-- saved from url=(0056)http://www.sunistudio.com/cppfaq/proper-inheritance.html -->
<HTML><HEAD><TITLE>[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312">
<META http-equiv=Content-Language content=zh-cn>
<META content=proper-inheritance.html name=FILENAME>
<META
content="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite"
name=ABSTRACT>
<META content=cline@parashift.com name=OWNER>
<META content="Marshall Cline, cline@parashift.com" name=AUTHOR>
<META content="MSHTML 6.00.2462.0" name=GENERATOR>
<META content=FrontPage.Editor.Document name=ProgId><LINK rev=made
href="mailto:cline@parashift.com"><LINK
href="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/cpp-faq.css"
type=text/css rel=stylesheet></HEAD>
<BODY>
<H1><A name=top></A>[21] 继承 — 彻底继承和可置换性<BR><SMALL><SMALL>(Part of <A
href="http://www.sunistudio.com/cppfaq/index.html"><EM>C++ FAQ Lite</EM></A>, <A
href="http://www.sunistudio.com/cppfaq/copy-permissions.html#[1.2]">Copyright ©
1991-2001</A>, <A href="http://www.parashift.com/" target=OutsideTheFAQ>Marshall
Cline</A>, <A
href="mailto:cline@parashift.com">cline@parashift.com</A>)</SMALL></SMALL></H1>
<P>简体中文版翻译:<A href="http://www.sunistudio.com/nicrosoft">申旻</A>,<A
href="mailto:nicrosoft@sunistudio.com">nicrosoft@sunistudio.com</A>(<A
href="http://www.sunistudio.com/">东日制作室</A>,<A
href="http://www.sunistudio.com/asp/sunidoc.asp">东日文档</A>)</P>
<HR>
<H3>FAQs in section [21]:</H3>
<UL>
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.1]">[21.1]
我应该隐藏基类的公有成员函数吗?</A>
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.2]">[21.2]
<TT>Derived*</TT> —> <TT>Base*</TT>
可以很好地工作; 为什么 <TT>Derived**</TT> —> <TT>Base**</TT> 不行?</A>
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.3]">[21.3]
parking-lot-of-Car(停车场)是一种 parking-lot-of-Vehicle(交通工具停泊场)吗?</A>
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.4]">[21.4]
<TT>Derived</TT>数组是一种 <TT>Base</TT>数组吗?</A><IMG alt=UPDATED!
src="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/updated.gif">
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.5]">[21.5]
派生类数组(array-of-<TT>Derived</TT>)“不是一种”基类数组(array-of-<TT>Base</TT>)是否意味着数组不好?</A><IMG
alt=UPDATED!
src="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/updated.gif">
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.6]">[21.6]
<TT>Circle</TT>(圆)是一种 <TT>Ellipse</TT>(椭圆)吗?</A>
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.7]">[21.7]
对于“圆是/不是一种椭圆”这个两难问题,有其它说法吗?</A>
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.8]">[21.8]
但我是数学博士,我相信圆是一种椭圆!这是否意味着Marshall Cline是傻瓜?或者C++是傻瓜?或者OO是傻瓜?</A>
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.9]">[21.9]
也许椭圆应该从圆继承?</A><IMG alt=NEW!
src="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/new.gif">
<LI><A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.10]">[21.10]
但我的问题与圆和椭圆无关,这种无聊的例子对我有什么好处?</A> </LI></UL>
<P>
<HR>
<P><A name=[21.1]></A>
<DIV class=FaqTitle>
<H3>[21.1] 我应该隐藏基类的公有成员函数吗?</H3></DIV>
<P>不要,不要,不要这样做。永远不要!<BR><BR>试图隐藏(消除、废除、私有化)继承而来的公有成员函数是非常常见的设计错误。通常这产生于浆糊脑袋。
<P>(注意: 本 FAQ 的论述仅与公有继承(<TT>public</TT> inheritance)有关; <A
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同)
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A>
| <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A>
| <A
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/abcs.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[21.2]></A>
<DIV class=FaqTitle>
<H3>[21.2] <TT>Derived*</TT> —> <TT>Base*</TT>
可以很好地工作; 为什么 <TT>Derived**</TT> —>
<TT>Base**</TT> 不行?</H3></DIV>
<P>由于<TT>Derived</TT>对象是一种<TT>Base</TT>对象,C++允许<TT>Derived*</TT> 转换成 <TT>Base*</TT>。然而,将 <TT>Derived**</TT><TT>
</TT>转换成 <TT>Base**
</TT>将产生错误。尽管这个错误不是显而易见的,这未尝不是件好事。例如,如果你能够将<TT>Car**</TT>转换成 <TT>Vehicle**</TT><TT>(译注:Vehicle意为交通工具)</TT>,并且如果你能同样的将<TT>NuclearSubmarine**(译注:NuclearSubmarine意为核潜艇)</TT> 转换成<TT>Vehicle**</TT>,那么你可能给这两个指针赋值,并最终使 <TT>Car*</TT> 指针指向 <TT>NuclearSubmarine</TT>:
<P>
<DIV
class=CodeBlock><TT> class Vehicle {<BR> public:<BR> virtual ~Vehicle() { }<BR> virtual void startEngine() = 0;<BR> };<BR> <BR> class Car : public Vehicle {<BR> public:<BR> virtual void startEngine();<BR> virtual void openGasCap();<BR> };<BR> <BR> class NuclearSubmarine : public Vehicle {<BR> public:<BR> virtual void startEngine();<BR> virtual void fireNuclearMissle();<BR> };<BR> <BR> int main()<BR> {<BR> Car car;<BR> Car* carPtr = &car;<BR> Car** carPtrPtr = &carPtr;<BR> Vehicle** vehiclePtrPtr = carPtrPtr; </TT><EM>// </EM><I>这在C++中是一个错误</I><TT><BR> NuclearSubmarine sub;<BR> NuclearSubmarine* subPtr = &sub;<BR> *vehiclePtrPtr = subPtr;<BR> </TT><EM>// 最后这行将导致carPtr指向 sub !</EM><TT><BR> carPtr->openGasCap(); </TT><EM>// 这将调用
<TT>fireNuclearMissle()</TT>! (译注:也就是发射核弹)</EM><TT><BR> } </TT></DIV>
<P>换句话说,如果从<TT>Derived**</TT> 到<TT>Base**</TT>的转换是合法的,那么<TT>Base**</TT>将可能被解除引用(易变的 <TT>Base*</TT>),并且 <TT>Base*</TT>可能被指向不同的派生类对象,这将导致严重的国家安全问题(天知道如果你调用了<TT>NuclearSubmarine</TT>(核潜艇)对象的 <TT>openGasCap()</TT>成员函数会发生什么!!而你却认为这是一个<TT>Car</TT>对象!!——试一下以上的代码,看看会发生什么——大多数的编译器会调用<TT>NuclearSubmarine::fireNuclearMissle()</TT>!
<P>(注意: 本 FAQ 的论述仅与公有继承(<TT>public</TT> inheritance)有关; <A
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同)
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A>
| <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A>
| <A
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/abcs.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[21.3]></A>
<DIV class=FaqTitle>
<H3>[21.3]
parking-lot-of-Car(停车场)是一种 parking-lot-of-Vehicle(交通工具停泊场)吗?</H3></DIV>
<P>不。
<P>我知道这听起来很奇怪,但这是事实。你可以将这看作为以上 FAQ的直接结论,或者你可以这样来理解:如果这个“是一种”关系成立的话,那么就可以将 parking-lot-of-Vehicle
类型的指针指向一个 parking-lot-of-Car。但是,parking-lot-of-Vehicle 有
<TT>addNewVehicleToParkingLot(Vehicle&)</TT>成员函数用来向停泊场添加任何
<TT>Vehicle</TT>(交通工具)对象。这样将允许你在 parking-lot-of-Car(停车场)停泊一个<TT>NuclearSubmarine</TT>(核潜艇)。当然,当某人认为从 parking-lot-of-Car
删除一个<TT>Car</TT>对象,而实际是一个<TT>NuclearSubmarine</TT>时,他会非常惊讶。
<P>用另一种方法阐述这个事实:一种事物的容器不是一种任何事物的容器。也许很难接受,但这是事实。
<P>你可以不喜欢它,但必须接受它。
<P>我们在OO/C++训练课程使用的最后一个例子:“一袋苹果不是一袋水果”。如果一袋苹果能够被传递给一袋水果的话,就可以把香蕉放入袋中,即使它被认为里面只能放苹果!
<P>(注意: 本 FAQ 的论述仅与公有继承(<TT>public</TT> inheritance)有关; <A
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同)
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A>
| <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A>
| <A
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/abcs.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[21.4]></A>
<DIV class=FaqTitle>
<H3>[21.4] <TT>Derived</TT>数组是一种 <TT>Base</TT>数组吗?<IMG alt=UPDATED!
src="[21] Inheritance proper inheritance and substitutability, C++ FAQ Lite.files/updated.gif"></H3></DIV><SMALL><EM>[Recently
changed so it uses new-style headers and the <TT>std::</TT> syntax and reworded
references to STL (on 7/00). <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#[21.5]">Click
here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[21.5]:rawtext--></A>.]</EM></SMALL>
<P>不。
<P>这是以上FAQ的结论。不幸的是它会把你带入困境,考虑一下这个:
<P>
<DIV
class=CodeBlock><TT> class Base {<BR> public:<BR> virtual void f(); </TT><EM>// 1</EM><TT><BR> };<BR> <BR> class Derived : public Base {<BR> public:<BR> </TT><EM>// ...</EM><TT><BR> private:<BR> int i_; </TT><EM>// 2</EM><TT><BR> };<BR> <BR> void userCode(Base* arrayOfBase)<BR> {<BR> arrayOfBase[1].f(); </TT><EM>// 3</EM><TT><BR> }<BR> <BR> int main()<BR> {<BR> Derived arrayOfDerived[10]; </TT><EM>// 4</EM><TT><BR> userCode(arrayOfDerived); </TT><EM>// 5</EM><TT><BR> } </TT></DIV>
<P>编译器会认为这是完美的类型安全。编号 5的这一行将 <TT>Derived*</TT> 转换为 <TT>Base*</TT>。但实际上这样做是可怕的:由于 <TT>Derived</TT>比<TT>Base</TT> 大,在编号3的这一行的指针运算是错误的:当编译器计算 <TT>arrayOfBase[1]</TT>的地址时使用 <TT>sizeof(Base)</TT>,而数组其实是一个<TT>Derived</TT>数组,这意味着在编号3的这一行的所计算的地址(以及之后的成员函数
<TT>f() </TT>的调用)并不在任何对象的起始位置!而在<TT>Derived</TT>对象的中间。假设你的编译器使用通常的方法寻找<A
href="http://www.sunistudio.com/cppfaq/virtual-functions.html">虚函数</A>,那么将导致第一个<TT>Derived</TT>对象的 <TT>int i_</TT> 被重新解释,将它看作指向虚函数表的指针,跟随着这个“指针”(意味着我们正在访问一个随机的内存位置),并将内存中那个位置的前几个字节解释为 C++成员函数的地址,然后将它们(随机的内存地址)装载到指令寄存器并开始从那个内存区产生机器指令。发生这样情况的几率相当高。
<P>根本问题是 C++无法区别指向事物的指针和指向事物数组的指针。自然的,C++是从C继承了这一特征。
<P>注意:如果我们使用类似数组(array-like)的类(例如,<A
href="http://www.sunistudio.com/cppfaq/class-libraries.html#[32.1]">标准库</A>中的<TT>std::vector<Derived></TT>)来代替原始的数组,这个问题将会被作为编译时错误找出而不是运行时的灾难。
<P>(注意: 本 FAQ 的论述仅与公有继承(<TT>public</TT> inheritance)有关; <A
href="http://www.sunistudio.com/cppfaq/private-inheritance.html">私有和保护继承</A>并不相同)
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#top">Top</A>
| <A
href="http://www.sunistudio.com/cppfaq/proper-inheritance.html#bottom">Bottom</A>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -