📄 effective c++ 2e item36.htm
字号:
class),它为派生类仅提供函数接口,完全没有实现。协议类在条款34中介绍过,并将在条款43再次提及。</P>
<P>简单虚函数的情况和纯虚函数有点不一样。照例,派生类继承了函数的接口,但简单虚函数一般还提供了实现,派生类可以选择改写它们或不改写它们。思考片刻就可以认识到:</P>
<P>· 声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。</P>
<P>具体到Shape::error,这个接口是在说,每个类必须提供一个出错时可以被调用的函数,但每个类可以按它们认为合适的任何方式处理错误。如果某个类不想做什么特别的事,可以借助于Shape类中提供的缺省出错处理函数。也就是说,Shape::error的声明是在告诉子类的设计者,"你必须支持error函数,但如果你不想写自己的版本,可以借助Shape类中的缺省版本。"</P>
<P>实际上,为简单虚函数同时提供函数声明和缺省实现是很危险的。想知道为什么,看看XYZ航空公司的这个飞机类的层次结构。XYZ公司只有两种飞机,A型和B型,而且两种机型的飞行方式完全一样。所以,XYZ设计了这样的层次结构:</P>
<P>class Airport { ... }; // 表示飞机</P>
<P>class Airplane {<BR>public:<BR> virtual void fly(const Airport&
destination);</P>
<P> ...</P>
<P>};</P>
<P>void Airplane::fly(const Airport& destination)<BR>{<BR>
飞机飞往某一目的地的缺省代码<BR>}</P>
<P>class ModelA: public Airplane { ... };</P>
<P>class ModelB: public Airplane { ... };</P>
<P>为了表明所有飞机都必须支持fly函数,而且因为不同型号的飞机原则上都需要对fly有不同的实现,
所以Airplane::fly被声明为virtual。但是,为了避免在ModelA类和ModelB类中写重复的代码,缺省的飞行行为是由Airplane::fly函数提供的,ModelA和ModelB继承了这一函数。</P>
<P>这是典型的面向对象设计。两个类享有共同的特征(实现fly的方式),所以这一共同特征被转移到基类,并让这两个类来继承这一特征。这种设计使得共性很清楚,避免了代码重复,将来容易增强功能,并易于长期维护
---- 所有这一切正是面向对象技术高度吹捧的。XYZ公司真得为此而骄傲。</P>
<P>现在假设XYZ公司发了大财,决定引进一种新型飞机,C型。C型和A型、B型有区别,特别是,飞行方式不一样。</P>
<P>XYZ的程序员在上面的层次结构中为C型增加了一个类,但因为急于使新型飞机投入使用,他们忘了重新定义fly函数:</P>
<P>class ModelC: public Airplane {</P>
<P>
...
// 没有声明fly函数<BR>};</P>
<P>然后,在程序中,他们做了类似下面的事:</P>
<P>Airport
JFK(...);
// JFK是纽约市的一个机场</P>
<P>Airplane *pa = new ModelC;</P>
<P>...</P>
<P>pa->fly(JFK);
// 调用Airplane::fly!</P>
<P>这将造成悲剧:竟然试图让ModelC对象如同ModelA或ModelB那样飞行。这种行为可不能换来旅客对你的信任!</P>
<P>这里的问题不在于Airplane::fly具有缺省行为,而在于ModelC可以不用明确地声明就可以继承这一行为。幸运的是,可以很容易做到为子类提供缺省行为、同时只是在子类想要的时候才给它们。窍门在于切断虚函数的接口和它的缺省实现之间的联系。下面是一种方法:</P>
<P>class Airplane {<BR>public:<BR> virtual void fly(const Airport&
destination) = 0;</P>
<P> ...</P>
<P>protected:<BR> void defaultFly(const Airport&
destination);<BR>};</P>
<P>void Airplane::defaultFly(const Airport& destination)<BR>{<BR>
飞机飞往某一目的地的缺省代码<BR>}</P>
<P>注意Airplane::fly已经变成了纯虚函数,它提供了飞行的接口。缺省实现还是存在于Airplane类中,但现在它是以一个独立函数(defaultFly)的形式存在的。ModelA和ModelB这些类想执行缺省行为的话,只用简单地在它们的fly函数体中对defaultFly进行一个内联调用(关于内联和虚函数间的相互关系,参见条款33):</P>
<P>class ModelA: public Airplane {<BR>public:<BR> virtual void fly(const
Airport& destination)<BR> { defaultFly(destination); }</P>
<P> ...</P>
<P>};</P>
<P>class ModelB: public Airplane {<BR>public:<BR> virtual void fly(const
Airport& destination)<BR> { defaultFly(destination); }</P>
<P> ...</P>
<P>};</P>
<P>对于ModelC类来说,它不可能无意间继承不正确的fly实现。因为Airplane中的纯虚函数强迫ModelC提供它自己版本的fly。</P>
<P>class ModelC: public Airplane {<BR>public:<BR> virtual void fly(const
Airport& destination);<BR> ...</P>
<P>};</P>
<P>void ModelC::fly(const Airport& destination)<BR>{<BR>
ModelC飞往某一目的地的代码<BR>}</P>
<P>这个方法不会万无一失(程序员还会因为 "拷贝粘贴"
而出错),但它比最初的设计可靠多了。至于Airplane::defaultFly被声明为protected,是因为它确实只是Airplane及其派生类的实现细节。使用airplane的用户只关心飞机能飞,而不会关心是怎么实现的。</P>
<P>Airplane::defaultFly是一个非虚函数也很重要。因为没有子类会重新定义这个函数,条款37说明了这一事实。如果defaultFly为虚函数,就会又回到这个问题:如果某些子类应该重新定义defaultFly而又忘记去做,那该怎么办?</P>
<P>一些人反对将接口和缺省实现作为单独函数分开,例如上面的fly和defaultFly。他们认为,起码这会污染类的名字空间,因为有这么多相近的函数名称在扩散。然而他们还是赞同接口和缺省实现应该分离。怎么解决这种表面上存在的矛盾呢?可以借助于这一事实:纯虚函数必须在子类中重新声明,但它还是可以在基类中有自己的实现。下面的Airplane正是利用这一点重新定义了一个纯虚函数:</P>
<P>class Airplane {<BR>public:<BR> virtual void fly(const Airport&
destination) = 0;</P>
<P> ...</P>
<P>};</P>
<P>void Airplane::fly(const Airport& destination)<BR>{<BR>
飞机飞往某一目的地的缺省代码<BR>}</P>
<P>class ModelA: public Airplane {<BR>public:<BR> virtual void fly(const
Airport& destination)<BR> { Airplane::fly(destination); }</P>
<P> ...</P>
<P>};</P>
<P>class ModelB: public Airplane {<BR>public:<BR> virtual void fly(const
Airport& destination)<BR> { Airplane::fly(destination); }</P>
<P> ...</P>
<P>};</P>
<P>class ModelC: public Airplane {<BR>public:<BR> virtual void fly(const
Airport& destination);</P>
<P> ...</P>
<P>};</P>
<P>void ModelC::fly(const Airport& destination)<BR>{<BR>
ModelC飞往某一目的地的代码<BR>}</P>
<P>这一设计和前面的几乎一样,只是纯虚函数Airplane::fly的函数体取代了独立函数Airplane::defaultFly。从本质上说,fly已经被分成两个基本部分了。它的声明说明了它的接口(派生类必须使用),而它的定义说明了它的缺省行为(派生类可能会使用,但要明确地请求)。然而,将fly和defaultFly合并后,就不再能够为这两个函数声明不同的保护级别了:本来是protected的代码(在defaultFly中)现在成了public(因为它在fly中)。</P>
<P>最后,来谈谈Shape的非虚函数,objectID。当一个成员函数为非虚函数时,它在派生类中的行为就不应该不同。实际上,非虚成员函数表明了一种特殊性上的不变性,因为它表示的是不会改变的行为
---- 不管一个派生类有多特殊。所以,</P>
<P>· 声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。</P>
<P>可以认为,Shape::objectID的声明就是在说,"每个Shape对象有一个函数用来产生对象的标识符,并且对象标识符的产生方式总是一样的。这种方式由Shape::objectID的定义决定,派生类不能改变它。"
因为非虚函数表示一种特殊性上的不变性,所以它决不能在子类中重新定义,关于这一点条款37进行了讨论。</P>
<P>理解了纯虚函数、简单虚函数和非虚函数在声明上的区别,就可以精确地指定你想让派生类继承什么:仅仅是接口,还是接口和一个缺省实现?或者,接口和一个强制实现?因为这些不同类型的声明指的是根本不同的事,所以在声明成员函数时一定要从中慎重选择。只有这样做,才可以避免没经验的程序员常犯的两个错误。</P>
<P>第一个错误是把所有的函数都声明为非虚函数。这就使得派生类没有特殊化的余地;非虚析构函数尤其会出问题(参见条款14)。当然,设计出来的类不准备作为基类使用也是完全合理的(条款M34就给出了一个你会这样做的例子)。这种情况下,专门声明一组非虚成员函数是适当的。但是,把所有的函数都声明为非虚函数,大多数情况下是因为对虚函数和非虚函数之间区别的无知,或者是过分担心虚函数对程序性能的影响(参见条款M24)。而事实上是:几乎任何一个作为基类使用的类都有虚函数(再次参见条款14)。</P>
<P>如果担心虚函数的开销,请允许我介绍80-20定律(参见条款M16)。它指出,在一个典型的程序中,80%的运行时间都花在执行20%的代码上。这条定律很重要,因为它意味着,平均起来,80%的函数调用可以是虚函数,并且它们不会对程序的整体性能带来哪怕一丁点可以觉察到的影响。所以,在担心是否承担得起虚函数的开销之前,不妨将注意力集中在那20%会真正带来影响的代码上。</P>
<P>另一个常见的问题是将所有的函数都声明为虚函数。有时这没错 ---- 比如,协议类(Protocol
class)就是证据(参见条款34)。但是,这样做往往表现了类的设计者缺乏表明坚定立场的勇气。一些函数不能在派生类中重定义,只要是这种情况,就要旗帜鲜明地将它声明为非虚函数。不能让你的函数好象可以为任何人做任何事
---- 只要他们花点时间重新定义所有的函数。记住,如果有一个基类B,一个派生类D,和一个成员函数mf,那么下面每个对mf的调用都必须工作正常:</P>
<P>D *pd = new D;<BR>B *pb = pd;</P>
<P>pb->mf();
// 通过基类指针调用mf</P>
<P>pd->mf();
// 通过派生类指针调用mf</P>
<P>有时,必须将mf声明为非虚函数才能保证一切都以你所期望的方式工作(参见条款37)。如果需要特殊性上的不变性,就大胆地说出来吧!</P><BR><BR></DIV></DIV></DIV><BR><BR>
<SCRIPT src="Effective C++ 2e Item36.files/get_readnum.htm"></SCRIPT>
<TABLE cellSpacing=1 cellPadding=2 width=770 align=center bgColor=#666666
border=0>
<TBODY>
<TR>
<TH id=white bgColor=#990000><FONT
color=#ffffff>对该文的评论</FONT></TH></TR></TBODY></TABLE><BR>
<SCRIPT language=javascript>
<!--
function isEmpty(s)
{
return ((s == null) || (s.length == 0))
}
function submit1()
{
if (isEmpty(document.add_critique.csdnname.value) || isEmpty(document.add_critique.csdnpassword.value) || isEmpty(document.add_critique.critique_content.value))
{
alert('登陆名,密码,评论不能为空!!!!') ;
return false;
}
document.add_critique.submit();
}
//-->
</SCRIPT>
<TABLE cellSpacing=1 cellPadding=2 width=770 align=center bgColor=#666666
border=0>
<TBODY>
<TR>
<TH id=white bgColor=#990000><FONT
color=#ffffff>发表评论</FONT></TH></TR></TBODY></TABLE>
<TABLE cellSpacing=1 cellPadding=2 width=770 align=center bgColor=#ffffff
border=0>
<TBODY>
<TR>
<TD>
<FORM name=add_critique action=/develop/add_critique.asp
method=post><INPUT type=hidden value=add name=critique_add> <INPUT
type=hidden value=9284 name=from> 评论人: <INPUT name=csdnname>
密码: <INPUT type=password name=csdnpassword>
评论:<BR> <TEXTAREA name=critique_content rows=8 cols=100></TEXTAREA><BR>
<INPUT onclick=javascript:submit1(); type=button value=发表评论 name=ubmit>
<INPUT type=hidden value=9284 name=id> </FORM></TD></TR></TBODY></TABLE>
<TABLE width=770 border=0>
<TBODY>
<TR>
<TD>
<TABLE cellPadding=2 width=770 border=0>
<TBODY>
<TR>
<TD height=10></TD></TR>
<TR>
<TD align=left width=130><A
href="http://www.csdn.net/news/looknews.asp?id=2313"
target=_blank><IMG src="http://www.csdn.net/job/images/csdn_job.gif"
border=0></A> </TD>
<TD align=middle width=510>
<OBJECT id=Movie1
codeBase=http://active.macromedia.com/flash2/cabs/swflash.cab#version=4,0,0,0
height=60 width=468
classid=clsid:D27CDB6E-AE6D-11cf-96B8-444553540000><PARAM NAME="movie" VALUE="http://www.csdn.net/images/ad/csdn_media.swf"><PARAM NAME="quality" VALUE="high">
<EMBED src="http://www.csdn.net/images/ad/csdn_media.swf"
quality=high WIDTH=468 HEIGHT=60
TYPE="application/x-shockwave-flash"
PLUGINSPAGE="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash">
</EMBED> </OBJECT></TD>
<TD align=middle width=130><A
href="http://www.hd315.gov.cn/beian/view.asp?bianhao=010202001032100010"><IMG
src="http://www.csdn.net/images/biaoshi.gif" border=0></A>
</TD></TR></TBODY></TABLE></TD></TR>
<TR>
<TD>
<TABLE height=20 cellSpacing=0 cellPadding=0 width=770 align=default
bgColor=#1f60a0 border=0>
<TBODY>
<TR class=font13px vAlign=center align=middle>
<TD class=font13px vAlign=center width=90 height=2></TD>
<TD width=2 rowSpan=2><IMG height=13
src="http://www.csdn.net/images/ad4.gif" width=2></TD>
<TD class=font13px width=90 height=2></TD>
<TD width=3 rowSpan=2><FONT color=#ffffff><IMG height=13
src="http://www.csdn.net/images/ad4.gif" width=2></FONT></TD>
<TD class=font13px width=90 height=2></TD>
<TD width=2 rowSpan=2><IMG height=13
src="http://www.csdn.net/images/ad4.gif" width=2></TD>
<TD class=font13px width=90 height=2></TD>
<TD width=3 rowSpan=2><FONT color=#ffffff><IMG height=13
src="http://www.csdn.net/images/ad4.gif" width=2></FONT></TD>
<TD width=350 height=2><FONT color=#ffffff></FONT></TD>
<TD width=10 rowSpan=2><FONT color=#ffffff><IMG height=11
src="http://www.csdn.net/images/ad3.gif" width=6></FONT></TD>
<TD class=font13px width=60 height=2></TD></TR>
<TR class=font14px vAlign=center align=middle>
<TD class=font13px vAlign=center width=90><A
href="http://www.csdn.net/intro/intro.shtm"><FONT
color=#ffffff>美达美简介</FONT></A></TD>
<TD class=font13px width=90><A
href="http://www.csdn.net/intro/ad.shtm"><FONT
color=#ffffff>广告服务</FONT></A></TD>
<TD class=font13px width=90><A
href="http://www.csdn.net/English/"><FONT
color=#ffffff>英语步步高</FONT></A></TD>
<TD class=font13px width=90><A href="http://www.csdn.net/dev/"><FONT
color=#ffffff>程序员大本营</FONT></A></TD>
<TD align=right width=350><SPAN class=font13px><A
href="mailto:webmaster@csdn.net"><FONT
color=#ffffff>百联美达美科技有限公司</FONT></A></SPAN><FONT color=#ffffff><SPAN
class=font13px> </SPAN></FONT></TD>
<TD class=font13px width=60><FONT color=#ffffff>版权所有</FONT>
<SCRIPT>document.write("<img src=http://202.106.156.10/stat.asp?user=designol&refer="+escape(document.referrer)+"&cur="+escape(document.URL)+" width=0 height=0 border=0>");</SCRIPT>
</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -