📄 ei39.htm
字号:
</UL><A NAME="7354"></A>
<UL><PRE> ... // as above
</PRE>
</UL><A NAME="7355"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7366"></A>
<P><A NAME="dingp19"></A>Graphically, it looks like <NOBR>this:<SCRIPT>create_link(19);</SCRIPT>
</NOBR></P>
<SPAN ID="Image1of1" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_177A1.GIF" BORDER=0></SPAN>
<SPAN ID="Image1of2" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_177A2.GIF" BORDER=0></SPAN>
<SPAN ID="Image1of3" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_177A3.GIF" BORDER=0></SPAN>
<SPAN ID="Image1of4" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_177A4.GIF" BORDER=0></SPAN>
<SPAN ID="Image1of5" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_177A5.GIF" BORDER=0></SPAN>
<SPAN ID="Image1of6" STYLE="position: relative; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_177A5.GIF" BORDER=0></SPAN>
<P><A NAME="dingp20"></A><A NAME="7367"></A>
Because both savings and checking accounts earn interest, you'd naturally like to move that common behavior up into a common base class. However, under the assumption that not all accounts in the bank will necessarily bear interest (certainly a valid assumption in my experience), you can't move it into the <CODE>BankAccount</CODE> class. As a result, you've introduced a new subclass of <CODE>BankAccount</CODE> called <CODE>InterestBearingAccount</CODE>, and you've made <CODE>SavingsAccount </CODE>and <CODE>CheckingAccount</CODE> inherit from <NOBR>it.<SCRIPT>create_link(20);</SCRIPT>
</NOBR></P>
<A NAME="7368"></A>
<P><A NAME="dingp21"></A>
The fact that both savings and checking accounts bear interest is indicated by the <CODE>InterestBearingAccount</CODE> pure virtual function <CODE>cred<A NAME="p178"></A>itInterest</CODE>, which is presumably redefined in its subclasses <CODE>SavingsAccount</CODE> and <CODE>CheckingAccount</CODE>.<SCRIPT>create_link(21);</SCRIPT>
</P>
<A NAME="7369"></A>
<P><A NAME="dingp22"></A>
This new class hierarchy allows you to rewrite your loop as <NOBR>follows:<SCRIPT>create_link(22);</SCRIPT>
</NOBR></P>
<A NAME="20313"></A>
<UL><PRE>// better, but still not perfect
for (list<BankAccount*>::iterator p = allAccounts.begin();
p != allAccounts.end();
++p) {
</PRE>
</UL><A NAME="20314"></A>
<UL><PRE> static_cast<InterestBearingAccount*>(*p)->creditInterest();
</PRE>
</UL><A NAME="20315"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="7374"></A>
<P><A NAME="dingp23"></A>
Although this loop still contains a nasty little cast, it's much more robust than it used to be, because it will continue to work even if new subclasses of <CODE>InterestBearingAccount</CODE> are added to your <NOBR>application.<SCRIPT>create_link(23);</SCRIPT>
</NOBR></P>
<A NAME="7375"></A>
<P><A NAME="dingp24"></A>
To get rid of the cast entirely, you must make some additional changes to your design. One approach is to tighten up the specification of your list of accounts. If you could get a list of <CODE>InterestBearingAccount</CODE> objects instead of <CODE>BankAccount</CODE> objects, everything would be <NOBR>peachy:<SCRIPT>create_link(24);</SCRIPT>
</NOBR></P>
<A NAME="7376"></A>
<UL><PRE>// all interest-bearing accounts in the bank
list<InterestBearingAccount*> allIBAccounts;
</PRE>
</UL><A NAME="20321"></A>
<UL><PRE>// a loop that compiles and works, both now and forever
for (list<InterestBearingAccount*>::iterator p =
allIBAccounts.begin();
p != allIBAccounts.end();
++p) {
</PRE>
</UL><A NAME="20322"></A>
<UL><PRE> (*p)->creditInterest();
</PRE>
</UL><A NAME="20323"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="7378"></A>
<P><A NAME="dingp25"></A>
If getting a more specialized list isn't an option, it might make sense to say that the <CODE>creditInterest</CODE> operation applies to all bank accounts, but that for non-interest-bearing accounts, it's just a no-op. That could be expressed this <NOBR>way:<SCRIPT>create_link(25);</SCRIPT>
</NOBR></P>
<A NAME="7380"></A>
<UL><PRE>class BankAccount {
public:
virtual void creditInterest() {}
</PRE>
</UL><A NAME="7381"></A>
<UL><PRE> ...
</PRE>
</UL><A NAME="7382"></A>
<UL><PRE>};
</PRE>
</UL><A NAME="7383"></A>
<UL><PRE>class SavingsAccount: public BankAccount { ... };</PRE>
</UL><A NAME="7384"></A>
<UL><PRE>class CheckingAccount: public BankAccount { ... };</PRE>
</UL><A NAME="219537"></A>
<UL><PRE>list<BankAccount*> allAccounts;</PRE>
</UL><A NAME="20341"></A>
<UL><PRE><A NAME="p179"></A>// look ma, no cast!
for (list<BankAccount*>::iterator p = allAccounts.begin();
p != allAccounts.end();
++p) {
</PRE>
</UL><A NAME="20342"></A>
<UL><PRE> (*p)->creditInterest();
</PRE>
</UL><A NAME="20335"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="20336"></A>
<P><A NAME="dingp26"></A>
Notice that the virtual function <CODE>BankAccount::creditInterest</CODE> provides an empty default implementation. This is a convenient way to specify that its behavior is a no-op by default, but it can lead to unforeseen difficulties in its own right. For the inside story on why, as well as how to eliminate the danger, consult <A HREF="./EI36_FR.HTM#7007" TARGET="_top">Item 36</A>. Notice also that <CODE>creditInterest</CODE> is (implicitly) an inline function. There's nothing wrong with that, but because it's also virtual, the inline directive will probably be ignored. <A HREF="./EI33_FR.HTM#6729" TARGET="_top">Item 33</A> explains <NOBR>why.<SCRIPT>create_link(26);</SCRIPT>
</NOBR></P>
<A NAME="7390"></A>
<P><A NAME="dingp27"></A>
As you have seen, downcasts can be eliminated in a number of ways. The best way is to replace such casts with calls to virtual functions, possibly also making each virtual function a no-op for any classes to which it doesn't truly apply. A second method is to tighten up the typing so that there is no ambiguity between the declared type of a pointer and the pointer type that you know is really there. Whatever the effort required to get rid of downcasts, it's effort well spent, because downcasts are ugly and error-prone, and they lead to code that's difficult to understand, enhance, and maintain (see <A HREF="../MEC/MI32_FR.HTM#5373" TARGET="_top">Item M32</A>).<SCRIPT>create_link(27);</SCRIPT>
</P>
<A NAME="7392"></A>
<P><A NAME="dingp28"></A>
What I've just written is the truth and nothing but the truth. It is not, however, the whole truth. There are occasions when you really do have to perform a <NOBR>downcast.<SCRIPT>create_link(28);</SCRIPT>
</NOBR></P>
<A NAME="20398"></A>
<P><A NAME="dingp29"></A>
For example, suppose you faced the situation we considered at the outset of this Item, i.e., <CODE>allAccounts</CODE> holds <CODE>BankAccount</CODE> pointers, <CODE>creditInterest</CODE> is defined only for <CODE>SavingsAccount</CODE> objects, and you must write a loop to credit interest to every account. Further suppose that all those things are beyond your control; you can't change the definitions for <CODE>BankAccount</CODE>, <CODE>SavingsAccount</CODE>, or <CODE>allAccounts</CODE>. (This would happen if they were defined in a library to which you had read-only access.) If that were the case, you'd have to use downcasting, no matter how distasteful you found the <NOBR>idea.<SCRIPT>create_link(29);</SCRIPT>
</NOBR></P>
<A NAME="20362"></A>
<P><A NAME="dingp30"></A>
Nevertheless, there is a better way to do it than through a raw cast such as we saw above. The better way is called "safe downcasting," and it's implemented via C++'s <CODE>dynamic_cast</CODE> operator (see <A HREF="../MEC/MI2_FR.HTM#77216" TARGET="_top">Item M2</A>). When you use <CODE>dynamic_cast</CODE> on a pointer, the cast is attempted, and if it succeeds (i.e., if the dynamic type of the pointer (see <A HREF="./EI38_FR.HTM#177948" TARGET="_top">Item 38</A>) is consistent with <A NAME="p180"></A>the type to which it's being cast), a valid pointer of the new type is returned. If the <CODE>dynamic_cast</CODE> fails, the null pointer is <NOBR>returned.<SCRIPT>create_link(30);</SCRIPT>
</NOBR></P>
<A NAME="7395"></A>
<P><A NAME="dingp31"></A>
Here's the banking example with safe downcasting <NOBR>added:<SCRIPT>create_link(31);</SCRIPT>
</NOBR></P>
<A NAME="20441"></A>
<UL><PRE>
class BankAccount { ... }; // as at the beginning of
// this Item
</PRE>
</UL><A NAME="20442"></A>
<UL><PRE>
class SavingsAccount: // ditto
public BankAccount { ... };
</PRE>
</UL><A NAME="20443"></A>
<UL><PRE>
class CheckingAccount: // ditto again
public BankAccount { ... };
</PRE>
</UL><A NAME="20467"></A>
<UL><PRE>
list<BankAccount*> allAccounts; // this should look
// familiar...
</PRE>
</UL><A NAME="7414"></A>
<UL><PRE>
void error(const string& msg); // error-handling function;
// see below
</PRE>
</UL><A NAME="20469"></A>
<UL><PRE>// well, ma, at least the casts are safe...
for (list<BankAccount*>::iterator p = allAccounts.begin();
p != allAccounts.end();
++p) {
</PRE>
</UL><A NAME="20498"></A>
<UL><PRE> // try safe-downcasting *p to a SavingsAccount*; see
// below for information on the definition of psa
if (SavingsAccount *psa =
dynamic_cast<SavingsAccount*>(*p)) {
psa->creditInterest();
}
</PRE>
</UL><A NAME="20506"></A>
<UL><PRE> // try safe-downcasting it to a CheckingAccount
else if (CheckingAccount *pca =
dynamic_cast<CheckingAccount*>(*p)) {
pca->creditInterest();
}
</PRE>
</UL><A NAME="20507"></A>
<UL><PRE> // uh oh — unknown account type
else {
error("Unknown account type!");
}
}
</PRE>
</UL><A NAME="20526"></A>
<P><A NAME="dingp32"></A>
This scheme is far from ideal, but at least you can detect when your downcasts fail, something that's impossible without the use of <CODE>dynamic_cast</CODE>. Note, however, that prudence dictates you also check for the case where <i>all</i> the downcasts fail. That's the purpose of the final <CODE>else</CODE> clause in the code above. With virtual functions, there'd be no need for such a test, because every virtual call must resolve to <i>some</i> function. When you start downcasting, however, all bets are off. If somebody added a new type of account to the hierarchy, for example, but failed to update the code above, all the downcasts would fail. <A NAME="p181"></A>That's why it's important you handle that possibility. In all likelihood, it's not supposed to be the case that all the casts can fail, but when you allow downcasting, bad things start to happen to good <NOBR>programmers.<SCRIPT>create_link(32);</SCRIPT>
</NOBR></P>
<A NAME="20591"></A>
<P><A NAME="dingp33"></A>
Did you check your glasses in a panic when you noticed what looks like variable definitions in the conditions of the <CODE>if</CODE> statements above? If so, worry not; your vision's fine. The ability to define such variables was added to the language at the same time as <CODE>dynamic_cast</CODE>. This feature lets you write neater code, because you don't really need <CODE>psa</CODE> or <CODE>pca</CODE> unless the <CODE>dynamic_cast</CODE>s that initialize them succeed, and with the new syntax, you don't have to define those variables outside the conditionals containing the casts. (<A HREF="./EI32_FR.HTM#25939" TARGET="_top">Item 32</A> explains why you generally want to avoid superfluous variable definitions, anyway.) If your compilers don't yet support this new way of defining variables, you can do it the old <NOBR>way:<SCRIPT>create_link(33);</SCRIPT>
</NOBR></P>
<A NAME="20593"></A>
<UL><PRE>for (list<BankAccount*>::iterator p = allAccounts.begin();
p != allAccounts.end();
++p) {
</PRE>
</UL><A NAME="20596"></A>
<UL><PRE>
SavingsAccount *psa; // traditional definition
CheckingAccount *pca; // traditional definition
</PRE>
</UL><A NAME="20584"></A>
<UL><PRE> if (psa = dynamic_cast<SavingsAccount*>(*p)) {
psa->creditInterest();
}
</PRE>
</UL><A NAME="20611"></A>
<UL><PRE> else if (pca = dynamic_cast<CheckingAccount*>(*p)) {
pca->creditInterest();
}
</PRE>
</UL><A NAME="20612"></A>
<UL><PRE> else {
error("Unknown account type!");
}
}
</PRE>
</UL><A NAME="20521"></A>
<P><A NAME="dingp34"></A>
In the grand scheme of things, of course, where you place your definitions for variables like psa and pca is of little consequence. The important thing is this: the <CODE>if</CODE>-<CODE>then</CODE>-<CODE>else</CODE> style of programming that downcasting invariably leads to is vastly inferior to the use of virtual functions, and you should reserve it for situations in which you truly have no alternative. With any luck, you will never face such a bleak and desolate programming <NOBR>landscape.<SCRIPT>create_link(34);</SCRIPT>
</NOBR></P>
<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> <BR> 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>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -