📄 ch07.htm
字号:
<tt>}</tt></pre><p>In general, all instances of the same type share a single <tt>type_info</tt> object. The most widely used member functions of <tt>type_info</tt> are <tt>name()</tt> and <tt>operator==</tt>. But before you can invoke these member functions, you have to access the <tt>type_info</tt> object itself. How is it done?</p><h3> <a name="Heading8">Operator typeid</a></h3><p>Operator <tt>typeid</tt> takes either an object or a type name as its argument and returns a matching <tt>const type_info</tt> object. The dynamic type of an object can be examined as follows:</p><pre><tt>OnRightClick (File & file) </tt><tt>{</tt><tt> if ( <b>typeid( file) == typeid( TextFile ) </b>)</tt><tt> {</tt><tt> //received a TextFile object; printing should be enabled</tt><tt> }</tt><tt> else</tt><tt> {</tt><tt> //not a TextFile object, printing disabled</tt><tt> }</tt><tt>}</tt></pre><p>To understand how it works, look at the highlighted source line:</p><pre><tt>if ( typeid( file) == typeid( TextFile ) ).</tt></pre><p>The <tt>if</tt> statement tests whether the dynamic type of the argument <tt>file</tt> is <tt>TextFile</tt> (the static type of <tt>file</tt> is <tt>File</tt>, of course). The leftmost expression, <tt>typeid(file)</tt>, returns a <tt>type_info </tt>object that holds the necessary runtime type information that is associated with the object <tt>file</tt>. The rightmost expression, <tt>typeid(TextFile)</tt>, returns the type information that is associated with class <tt>TextFile</tt>. When <tt>typeid</tt> is applied to a class name rather than an object, it always returns a <tt>type_info</tt> object that corresponds to that class name. As you saw earlier, <tt>type_info</tt> overloads the operator <tt>==</tt>. Therefore, the <tt>type_info</tt> object that is returned by the leftmost <tt>typeid</tt> expression is compared to the <tt>type_info</tt> object that is returned by the rightmost <tt>typeid</tt> expression. If indeed <tt>file</tt> is an instance of <tt>TextFile</tt>, the <tt>if</tt> statement evaluates to <tt>true</tt>. In this case, <tt>OnRightClick</tt> displays an additional option in the menu -- <tt>print()</tt>. If, on the other hand, <tt>file</tt> is not a <tt>TextFile</tt>, the <tt>if</tt> statement evaluates to <tt>false</tt>, and the <tt>print()</tt> option is disabled. This is all well and good, but a <tt>typeid</tt>-based solution has a drawback. Suppose that you want to add support for a new type of files, for example HTML files. What happens when the file manager application has to be extended? HTML files are essentially text files. They can be read and printed. However, they differ from plain text files in some respects. An <tt>open</tt> message applied to an HTML file launches a browser rather than a word processor. In addition, HTML files have to be converted to a printable format before they can be printed. The need to extend a system's functionality at a minimal cost is a challenge that is faced by software developers every day. Object-oriented programming and design can facilitate the task. By subclassing <tt>TextFile</tt>, you can reuse its existing behavior and implement only the additional functionality that is required for HTML files:</p><pre><tt>class HTMLFile : public TextFile</tt><tt>{</tt><tt> void open () { Launch_Browser (); } </tt><tt> void virtual print(); // perform the necessary conversions to a </tt><tt> //printable format and then print file</tt><tt>};</tt></pre><p>This is, however, only half of the story. <tt>OnRightClick()</tt> fails badly when it receives an object of type <tt>HTMLFile</tt>. Look at it again to see why:</p><pre><tt>OnRightClick (File & file) //operating system's API function</tt><tt>{</tt><tt> if ( <b>typeid( file) == typeid( TextFile ) </b>)</tt><tt> {</tt><tt> //we received a TextFile object; printing should be enabled</tt><tt> }</tt><tt> else //OOPS! we get here when file is of type HTMLFile</tt><tt> {</tt><tt> }</tt><tt>}</tt></pre><p><tt>typeid</tt> returns the exact type information of its argument. Therefore, the <tt>if</tt> statement in <tt>OnRightClick()</tt> evaluates to <tt>false</tt> when the argument is an <tt>HTMLFile</tt>. But a <tt>false</tt> value implies a binary file! Consequently, printing is disabled. This onerous bug is likely to occur every time you add support for a new file type. Of course, you can modify <tt>OnRightClick()</tt> so that it performs another test:</p><pre><tt>OnRightClick (File & file) //operating system's API function</tt><tt>{</tt><tt> if ( (<b>typeid( file) == typeid( TextFile )) </b></tt><tt><b> || (typeid( file) == typeid( HTMLFile)) </b>) //check for HTMLFile as well</tt><tt> {</tt><tt> //we received either a TextFile or an HTMLFile; printing should be enabled</tt><tt> }</tt><tt> else //it's a binary file, no print option</tt><tt> {</tt><tt> }</tt><tt>}</tt></pre><p>However, this solution is cumbersome and error prone. Furthermore, it imposes an unacceptable burden on the programmers who maintain this function. Not only are they required to clutter up <tt>OnRightClick()</tt> with additional code every time a new class is derived from <tt>File</tt>, but they also have to be on guard to detect any new class that has been derived from <tt>File</tt> lately. Fortunately, C++ offers a much better way to handle this situation.</p><blockquote> <hr> <strong>NOTE: </strong> You can use <tt>typeid</tt> to retrieve the type information of non-polymorphic objects and fundamental types. However, the result refers to a <tt>type_info</tt> object that represents the static type of the operand. For example <hr></blockquote><pre><tt>#include<typeinfo></tt><tt>#include <iostream></tt><tt>#include <string></tt><tt>using namespace std;</tt><tt>typedef int I;</tt><tt>void fundamental()</tt><tt>{</tt><tt> cout<<typeid(I).name()<<endl; //display 'int'</tt><tt>}</tt><tt>void non_polymorphic()</tt><tt>{</tt><tt> cout<<typeid(string).name()<<endl;</tt><tt>}</tt></pre><blockquote> <hr> <strong>NOTE: </strong> Note however, that applying <tt>dynamic_cast</tt> to fundamental types or non-polymorphic classes is a compile time error. <hr></blockquote><p></p><h3> <a name="Heading9">Operator dynamic_cast<></a></h3><p>It is a mistake to allow <tt>OnRightClick()</tt> to take care of every conceivable class type. In doing so, you are forced to modify <tt>OnRightClick()</tt> any time you add a new file class or modify an existing class. In software design, and in object-oriented design in particular, you want to minimize such dependencies. If you examine <tt>OnRightClick()</tt> closely, you can see that it doesn't really know whether its argument is an instance of class <tt>TextFile</tt> (or of any other class, for that matter). Rather, all it needs to know is whether its argument is a <tt>TextFile</tt>. There is a big difference between the two -- an object <i>is-a</i> <tt>TextFile</tt> if it is an instance of class <tt>TextFile</tt> or if it is an instance of any class derived from <tt>TextFile</tt>. However, <tt>typeid</tt> is incapable of examining the derivation hierarchy of an object. For this purpose, you have to use the operator <tt>dynamic_cast<></tt>. <tt>dynamic_cast<></tt> takes two arguments: The first is a type name, and the second argument is an object, which <tt>dynamic_cast<></tt> attempts to cast at runtime to the desired type. For example</p><pre><tt>dynamic_cast <TextFile &> (file); //attempt to cast file to a reference to </tt><tt> //an object of type TextFile</tt></pre><p>If the attempted cast succeeds, either the second argument is an instance of the class name that appears as the second argument or it is an object derived from it. The preceding <tt>dynamic_cast<></tt> expression succeeds if <tt>file</tt> <i>is-a</i> <tt>TextFile</tt>. This is exactly the information needed by <tt>OnRightClick</tt> to operate properly. But how do you know whether <tt>dynamic_cast<></tt> was successful?</p><h4> Pointer Cast and Reference Cast</h4><p>There are two flavors of <tt>dynamic_cast<></tt>. One uses pointers and the other uses references. Accordingly, <tt>dynamic_cast<></tt> returns a pointer or a reference of the desired type when it succeeds. When <tt>dynamic_cast<></tt> cannot perform the cast, it returns a <tt>NULL</tt> pointer or, in the case of a reference, it throws an exception of type <tt>std::bad_cast.</tt> Look at the following pointer cast example:</p><pre><tt>TextFile * pTest = dynamic_cast < TextFile *> (&file); //attempt to cast </tt><tt> //file address to a pointer to TextFile</tt><tt>if (pTest) //dynamic_cast succeeded, file is-a TextFile</tt><tt>{</tt><tt> //use pTest</tt><tt>}</tt><tt>else // file is not a TextFile; pTest has a NULL value</tt><tt>{</tt><tt>}</tt></pre><p>C++ does not have <tt>NULL</tt> references. Therefore, when a reference <tt>dynamic_cast<></tt> fails, it throws an exception of type <tt>std::bad_cast</tt>. That is why you always need to place a reference <tt>dynamic_cast<></tt> expression within a <tt>try</tt>-block and include a suitable <tt>catch</tt>-statement to handle <tt>std::bad_cast</tt> exceptions (see also Chapter 6, "Exception Handling"). For example</p><pre><tt>try</tt><tt>{</tt><tt> TextFile tf = dynamic_cast < TextFile &> (file); </tt><tt> //use tf safely,</tt><tt>}</tt><tt>catch (std::bad_cast)</tt><tt>{ </tt><tt> //dynamic_cast<> failed</tt><tt>}</tt></pre><p>Now you can revise <tt>OnRightClick()</tt> to handle <tt>HTMLFile</tt> objects properly:</p><pre><tt>OnRightClick (File & file) </tt><tt>{</tt><tt> try</tt><tt> {</tt><tt> TextFile temp = dynamic_cast<TextFile&> (file);</tt><tt> //display options, including "print"</tt><tt> switch (message)</tt><tt> {</tt><tt> case m_open:</tt><tt> temp.open(); //either TextFile::open or HTMLFile::open </tt><tt> break;</tt><tt> case m_print:</tt><tt> temp.print();//either TextFile::print or HTMLFile::print</tt><tt> break;</tt><tt> }//switch</tt><tt> }//try</tt><tt> catch (std::bad_cast& noTextFile)</tt><tt> {</tt><tt> // treat file as a BinaryFile; exclude"print"</tt><tt> }</tt><tt>}// OnRightClick</tt></pre><p>The revised version of <tt>OnRightClick()</tt> handles an object of type <tt>HTMLFile</tt> appropriately because an object of type <tt>HTMLFile</tt> <i>is-a</i> <tt>TextFile</tt>. When the user clicks on the open message in the file manager application, the function <tt>OnRightClick()</tt> invokes the member function <tt>open()</tt> of its argument, which behaves as expected because it was overridden in class <tt>HTMLFile</tt>. Likewise, when <tt>OnRightClick()</tt> detects that its argument is a TextFile, it displays a print option. If the user clicks on this option, <tt>OnRightClick()</tt> sends the message <tt>print</tt> to its argument, which reacts as expected.</p><h3> <a name="Heading10">Other Uses of dynamic_cast<></a></h3>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -