⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ei39.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">
<HTML LANG="EN">
<HEAD>
<title>Effective C++, 2E | Item 39: Avoid casts down the inheritance hierarchy</TITLE>
<LINK REL=STYLESHEET HREF=../INTRO/ECMEC.CSS>

<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/COOKIE.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/IMGDOC.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/NSIMGDOC.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">var imagemax = 2; setCurrentMax(2);</SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/DINGBATS.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">
var dingbase = "EI39_DIR.HTM";
var dingtext = "Item E39, P";
if (self == top) {
 top.location.replace(dingbase + this.location.hash);
}
</SCRIPT>

</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" ONLOAD="setResize()">
<!-- SectionName="E39: Avoid casts down the inheritance hierarchy" -->
<A NAME="7269"></A>

<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI38_FR.HTM" TARGET="_top">Item 38:  Never redefine an inherited default parameter value. </A> &nbsp;&nbsp;<BR>&nbsp;&nbsp;Continue to <A HREF="./EI40_FR.HTM" TARGET="_top">Item 40: Model "has-a" or "is-implemented-in-terms-of" through layering.</A></FONT></DIV>


<P><A NAME="dingp1"></A><FONT ID="eititle">Item 39: &nbsp;Avoid casts down the inheritance hierarchy.</FONT><SCRIPT>create_link(1);</SCRIPT>
</P>

<A NAME="7270"></A>
<P><A NAME="dingp2"></A>
In these tumultuous economic times, it's a good idea to keep an eye on our financial institutions, so consider a Protocol class (see <A HREF="./EI34_FR.HTM#6793" TARGET="_top">Item 34</A>) for bank <NOBR>accounts:<SCRIPT>create_link(2);</SCRIPT>
</NOBR></P>
<A NAME="7275"></A>
<UL><PRE><A NAME="p174"></A>class Person { ... };
</PRE>
</UL><A NAME="7277"></A>
<UL><PRE>class BankAccount {
public:
  BankAccount(const Person *primaryOwner,
              const Person *jointOwner);
  virtual ~BankAccount();
</PRE>
</UL><A NAME="7278"></A>
<UL><PRE>  virtual void makeDeposit(double amount) = 0;
  virtual void makeWithdrawal(double amount) = 0;
</PRE>
</UL><A NAME="7279"></A>
<UL><PRE>  virtual double balance() const = 0;
</PRE>
</UL><A NAME="7280"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7281"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7282"></A>
<P><A NAME="dingp3"></A>
Many banks now offer a bewildering array of account types, but to keep things simple, let's assume there is only one type of bank account, namely, a savings <NOBR>account:<SCRIPT>create_link(3);</SCRIPT>
</NOBR></P>
<A NAME="7284"></A>
<UL><PRE>class SavingsAccount: public BankAccount {
public:
  SavingsAccount(const Person *primaryOwner,
                 const Person *jointOwner);
  ~SavingsAccount();
</PRE>
</UL><A NAME="7285"></A>
<UL><PRE>
  void creditInterest();                // add interest to account
</PRE>
</UL><A NAME="7286"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7287"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7288"></A>
<P><A NAME="dingp4"></A>
This isn't much of a savings account, but then again, what is these days? At any rate, it's enough for our <NOBR>purposes.<SCRIPT>create_link(4);</SCRIPT>
</NOBR></P>
<A NAME="20141"></A>
<P><A NAME="dingp5"></A>
A bank is likely to keep a list of all its accounts, perhaps implemented via the <CODE>list</CODE> class template from the standard library (see <A HREF="./EI49_FR.HTM#8392" TARGET="_top">Item 49</A>). Suppose this list is imaginatively named <CODE>allAccounts</CODE>:<SCRIPT>create_link(5);</SCRIPT>
</P>
<A NAME="7306"></A>
<UL><PRE>
list&lt;BankAccount*&gt; allAccounts;         // all accounts at the
                                        // bank
</PRE>
</UL><A NAME="20124"></A>
<P><A NAME="dingp6"></A>
Like all standard containers, <CODE>list</CODE>s store <i>copies</i> of the things placed into them, so to avoid storing multiple copies of each <CODE>BankAccount</CODE>, the bank has decided to have <CODE>allAccounts</CODE> hold <i>pointers</i> to <CODE>BankAccount</CODE>s instead of <CODE>BankAccount</CODE>s <NOBR>themselves.<SCRIPT>create_link(6);</SCRIPT>
</NOBR></P>
<A NAME="7307"></A>
<P><A NAME="dingp7"></A>
Now imagine you're supposed to write the code to iterate over all the accounts, crediting the interest due each one. You might try <NOBR>this,<SCRIPT>create_link(7);</SCRIPT>
</NOBR></P>
<A NAME="7308"></A>
<UL><PRE><A NAME="p175"></A>// a loop that won't compile (see below if you've never
// seen code using "iterators" before)
for (list&lt;BankAccount*&gt;::iterator p = allAccounts.begin();
      p != allAccounts.end();
      ++p) {
</PRE>
</UL><A NAME="20243"></A>
<UL><PRE>  (*p)-&gt;creditInterest();      // error!
</PRE>
</UL><A NAME="213612"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="213613"></A>
<P><A NAME="dingp8"></A>
but your compilers would quickly bring you to your senses: <CODE>allAccounts</CODE> contains pointers to <CODE>BankAccount</CODE> objects, not to <CODE>SavingsAccount</CODE> objects, so each time around the loop, <CODE>p</CODE> points to a <CODE>BankAccount</CODE>. That makes the call to <CODE>creditInterest</CODE> invalid, because <CODE>creditInterest</CODE> is declared only for <CODE>SavingsAccount</CODE> objects, not <CODE>BankAccount</CODE>s.<SCRIPT>create_link(8);</SCRIPT>
</P>
<A NAME="222367"></A>
<P><A NAME="dingp9"></A>
If "<CODE>list&lt;BankAccount*&gt;::iterator</CODE> <CODE>p</CODE> <CODE>=</CODE> <CODE>allAccounts.begin()</CODE>" looks to you more like transmission line noise than C++, you've apparently never had the pleasure of meeting the container class templates in the standard library. This part of the library is usually known as the Standard Template Library (the "STL"), and you can get an overview of it in Items <A HREF="./EI49_FR.HTM#8392" TARGET="_top">49</A> and <A HREF="../MEC/MI35_FR.HTM#5473" TARGET="_top">M35</A>. For the time being, all you need to know is that the variable <CODE>p</CODE> acts like a pointer that loops through the elements of <CODE>allAccounts</CODE> from beginning to end. That is, <CODE>p</CODE> acts as if its type were <CODE>BankAccount**</CODE> and the list elements were stored in an <NOBR>array.<SCRIPT>create_link(9);</SCRIPT>
</NOBR></P>
<A NAME="7310"></A>
<P><A NAME="dingp10"></A>
It's frustrating that the loop above won't compile. Sure, <CODE>allAccounts</CODE> is defined as holding <CODE>BankAccount*</CODE>s, but you <i>know</i> that it actually holds <CODE>SavingsAccount*</CODE>s in the loop above, because <CODE>SavingsAccount</CODE> is the only class that can be instantiated. Stupid compilers! You decide to tell them what you know to be obvious and what they are too dense to figure out on their own: <CODE>allAccounts</CODE> really contains <CODE>SavingsAccount*</CODE>s:<SCRIPT>create_link(10);</SCRIPT>
</P>
<A NAME="7311"></A>
<UL><PRE>// a loop that will compile, but that is nonetheless evil
for (list&lt;BankAccount*&gt;::iterator p = allAccounts.begin();
      p != allAccounts.end();
      ++p) {
</PRE>
</UL><A NAME="20233"></A>
<UL><PRE>  static_cast&lt;SavingsAccount*&gt;(*p)-&gt;creditInterest();
</PRE>
</UL><A NAME="20245"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="7312"></A>
<P><A NAME="dingp11"></A>
All your problems are solved! Solved clearly, solved elegantly, solved concisely, all by the simple use of a cast. You know what type of pointer <CODE>allAccounts</CODE> really holds, your dopey compilers don't, so you use a cast to tell them. What could be more <NOBR>logical?<SCRIPT>create_link(11);</SCRIPT>
</NOBR></P>
<A NAME="7313"></A>
<P><A NAME="dingp12"></A>
<A NAME="p176"></A>There is a biblical analogy I'd like to draw here. Casts are to C++ programmers what the apple was to <NOBR>Eve.<SCRIPT>create_link(12);</SCRIPT>
</NOBR></P>
<A NAME="7316"></A>
<P><A NAME="dingp13"></A>
This kind of cast &#151; from a base class pointer to a derived class pointer &#151; is called a <i>downcast</i>, because you're casting down the inheritance hierarchy. In the example you just looked at, downcasting happens to work, but it leads to a maintenance nightmare, as you will soon <NOBR>see.<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P>
<A NAME="20260"></A>
<P><A NAME="dingp14"></A>
But back to the bank. Buoyed by the success of its savings accounts, let's suppose the bank decides to offer checking accounts, too. Furthermore, assume that checking accounts also bear interest, just like savings <NOBR>accounts:<SCRIPT>create_link(14);</SCRIPT>
</NOBR></P>
<A NAME="7327"></A>
<UL><PRE>class CheckingAccount: public BankAccount {
public:
  void creditInterest();    // add interest to account
</PRE>
</UL><A NAME="7328"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7329"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7330"></A>
<P><A NAME="dingp15"></A>
Needless to say, <CODE>allAccounts</CODE> will now be a list containing pointers to both savings and checking accounts. Suddenly, the interest-crediting loop you wrote above is in serious <NOBR>trouble.<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<A NAME="7331"></A>
<P><A NAME="dingp16"></A>
Your first problem is that it will continue to compile without your changing it to reflect the existence of <CODE>CheckingAccount</CODE> objects. This is because compilers will foolishly believe you when you tell them (through the <CODE>static_cast</CODE>) that <CODE>*p</CODE> really points to a <CODE>SavingsAccount*</CODE>. After all, you're the boss. That's Maintenance Nightmare Number One. Maintenance Nightmare Number Two is what you're tempted to do to fix the problem, which is typically to write code like <NOBR>this:<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P>
<A NAME="20277"></A>
<UL><PRE>for (list&lt;BankAccount*&gt;::iterator p = allAccounts.begin();
      p != allAccounts.end();
      ++p) {
</PRE>
</UL><A NAME="20278"></A>
<UL><PRE>  if (*p <i>points to a SavingsAccount</i>)
    static_cast&lt;SavingsAccount*&gt;(*p)-&gt;creditInterest();
  else
    static_cast&lt;CheckingAccount*&gt;(*p)-&gt;creditInterest();
</PRE>
</UL><A NAME="20297"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="7339"></A>
<P><A NAME="dingp17"></A>
Anytime you find yourself writing code of the form, "if the object is of type T1, then do something, but if it's of type T2, then do something else," slap yourself. That isn't The C++ Way. Yes, it's a reasonable strategy in C, in Pascal, even in Smalltalk, but not in C++. In C++, you use virtual <NOBR>functions.<SCRIPT>create_link(17);</SCRIPT>
</NOBR></P>
<A NAME="7342"></A>
<P><A NAME="dingp18"></A>
<A NAME="p177"></A>Remember that with a virtual function, <i>compilers</i> are responsible for making sure that the right function is called, depending on the type of the object being used. Don't litter your code with conditionals or switch statements; let your compilers do the work for you. Here's <NOBR>how:<SCRIPT>create_link(18);</SCRIPT>
</NOBR></P>
<A NAME="7343"></A>
<UL><PRE>class BankAccount { ... };      // as above
</PRE>
</UL><A NAME="7344"></A>
<UL><PRE>// new class representing accounts that bear interest
class InterestBearingAccount: public BankAccount {
public:
  virtual void creditInterest() = 0;
</PRE>
</UL><A NAME="7346"></A>
<UL><PRE>  ...
</PRE>
</UL><A NAME="7347"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7349"></A>
<UL><PRE>class SavingsAccount: public InterestBearingAccount {
</PRE>
</UL><A NAME="7350"></A>
<UL><PRE>  ...                           // as above
</PRE>
</UL><A NAME="7351"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7353"></A>
<UL><PRE>class CheckingAccount: public InterestBearingAccount {
</PRE>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -