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

📄 ch07.htm

📁 c++语言操作手册
💻 HTM
📖 第 1 页 / 共 3 页
字号:
<p>Dynamic type casts are required in cases in which the dynamic type of an object   -- rather than its static type -- is necessary to perform the cast properly.   Note that any attempt to use a static cast in these cases is either flagged   as an error by the compiler, or -- even worse -- it might result in undefined   behavior at runtime.</p><h4> Cross casts</h4><p>A <i>cross cast</i> converts a multiply-inherited object to one of its secondary   base classes. To demonstrate what a cross cast does, consider the following   class hierarchy:</p><pre><tt>struct A</tt><tt>{</tt><tt>  int i;</tt><tt>  virtual ~A () {} //enforce polymorphism; needed for dynamic_cast</tt><tt>};</tt><tt>struct B</tt><tt>{</tt><tt>  bool b;</tt><tt>};</tt><tt>struct D: public A, public B</tt><tt>{</tt><tt>  int k;</tt><tt>  D() { b = true; i = k = 0; } </tt><tt>};</tt><tt>A *pa = new D;</tt><tt>B *pb = dynamic_cast&lt;B*&gt; pa;  //cross cast; access the second base </tt><tt>                              //of a multiply-derived object</tt></pre><p>The static type of <tt>pa</tt> is "pointer to <tt>A</tt>", whereas its dynamic   type is "pointer to <tt>D</tt>". A simple <tt>static_cast&lt;&gt;</tt> cannot   convert a pointer to <tt>A</tt> into a pointer to <tt>B</tt> because <tt>A</tt>   and <tt>B</tt> are unrelated (your compiler issues an error message in this   case). A brute force cast, (for example <tt>reinterpret_cast&lt;&gt;</tt> or   C-style cast), has disastrous results at runtime because the compiler simply   assigns <tt>pa</tt> to <tt>pb</tt>. However, the <tt>B</tt> subobject is located   at a different address within <tt>D</tt> than the <tt>A</tt> subobject. To perform   the cross cast properly, the value of <tt>pb</tt> has to be calculated at runtime.   After all, the cross cast can be done in a translation unit that doesn't even   know that class <tt>D</tt> exists! The following program demonstrates why a   dynamic cast, rather than compile-time cast, is required:</p><pre><tt>int main()</tt><tt>{</tt><tt>  A *pa = new D;</tt><tt>  B *pb = (B*) pa;  // disastrous; pb points to the subobject A within d</tt><tt>  bool bb = pb-&gt;b;  // bb has an undefined value</tt><tt>  cout&lt;&lt; "pa: " &lt;&lt; pa &lt;&lt; " pb: "&lt;&lt;pb &lt;&lt;endl;  // pb was not properly </tt><tt>                                               //adjusted; pa and pb are identical</tt><tt>  pb = dynamic_cast&lt;B*&gt; (pa); //cross cast; adjust pb correctly</tt><tt>  bb= pb-&gt;b; //OK, bb is true</tt><tt>  cout&lt;&lt; "pa: "&lt;&lt; pa &lt;&lt; " pb: " &lt;&lt; pb &lt;&lt;endl; // OK, pb was properly adjusted;</tt><tt>                                             // pa and pb have distinct values</tt><tt>  return 0;</tt><tt>}</tt></pre><p>The program displays two lines of output; the first shows that the memory addresses   of <tt>pa</tt> and <tt>pb</tt> are identical. The second line shows that the   memory addresses of <tt>pa</tt> and <tt>pb</tt> are different after performing   a dynamic cast as required.</p><h4> Downcasting From a Virtual Base</h4><p>A <i>downcast</i> is a cast from a base to a derived object. Before the introduction   of RTTI to the language, downcasts were regarded as a bad programming practice.   They were unsafe, and some even viewed the reliance on the dynamic type of an   object a violation of object-oriented principles (see also Chapter 2, "Standard   Briefing: the Latest Addenda to ANSI/ISO C++"). <tt>dynamic_cast&lt;&gt;</tt>   enables you to use safe, standardized, and simple downcasts from a virtual base   to its derived object. Look at the following example:</p><pre><tt> struct V</tt><tt>{</tt><tt>  virtual ~V (){} //ensure polymorphism</tt><tt>};</tt><tt>struct A: virtual V {};</tt><tt>struct B: virtual V {};</tt><tt>struct D: A, B {};</tt><tt>#include &lt;iostream&gt;</tt><tt>using namespace std;</tt><tt>int main()</tt><tt>{</tt><tt> V *pv = new D;</tt><tt> A* pa = dynamic_cast&lt;A*&gt; (pv); // downcast</tt><tt> cout&lt;&lt; "pv: "&lt;&lt; pv &lt;&lt; " pa: " &lt;&lt; pa &lt;&lt;endl; // OK, pv and pa have </tt><tt>                                             //different addresses</tt><tt> return 0;</tt><tt>}</tt></pre><p><tt>V</tt> is a virtual base for classes <tt>A</tt> and <tt>B</tt>. <tt>D</tt>   is multiply-inherited from <tt>A</tt> and <tt>B</tt>. Inside <tt>main()</tt>,   <tt>pv</tt> is declared as a "pointer to <tt>V</tt>" and its dynamic type is   "pointer to <tt>D</tt>". Here again, as in the cross cast example, the dynamic   type of <tt>pv</tt> is needed in order to properly downcast it to a pointer   to <tt>A</tt>. A <tt>static_cast&lt;&gt;</tt> would be rejected by the compiler.   As you read in Chapter 5, the memory layout of a virtual subobject might be   different from that of a nonvirtual subobject. Consequently, it is impossible   to calculate at compile time the address of the subobject <tt>A</tt> within   the object pointed to by <tt>pv</tt>. As the output of the program shows, <tt>pv</tt>   and <tt>pa</tt> indeed point to different memory addresses.</p><h2> <a name="Heading11">The Cost of Runtime Type Information</a></h2><p>Runtime Type Information is not free. To estimate how expensive it is in terms   of performance, it is important to understand how it is implemented behind the   scenes. Some of the technical details are platform-dependent. Still, the basic   model that is presented here can give you a fair idea of the performance penalties   of RTTI in terms of memory overhead and execution speed.</p><h3> <a name="Heading12">Memory Overhead</a></h3><p>Additional memory is needed to store the <tt>type_info</tt> object of every   fundamental and user-defined type. Ideally, the implementation associates a   single <tt>type_info</tt> object with every distinct type. However, this is   not a requirement, and under some circumstances -- for example, dynamically   linked libraries -- it is impossible to guarantee that only one <tt>type_info</tt>   object per class exists. . Therefore, an implementation can create more than   one <tt>type_info</tt> object per type.</p><p>As was previously noted, there is a practical reason that <tt>dynamic_cast&lt;&gt;</tt>   is applicable only to polymorphic objects: An object does not store its runtime   type information directly (as a data member, for example). </p><h3> <a name="Heading13">Runtime Type Information of Polymorphic Objects</a></h3><p>Every polymorphic object has a pointer to its virtual functions table. This   pointer, traditionally named <tt>vptr,</tt> holds the address of a dispatch   table that contains the memory addresses of every virtual function in this class.   The trick is to add another entry to this table. This entry points at the class's   <tt>type_info</tt> object. In other words, the <tt>vptr</tt> data member of   a polymorphic object points at a table of pointers, in which the address of   <tt>type_info</tt> is kept at a fixed position. This model is very economical   in terms of memory usage; it requires a single <tt>type_info</tt> object and   a pointer for every polymorphic class. Note that this is a fixed cost, regardless   of how many instances of the class actually exist in the program. The cost of   retrieving an object's runtime type information is therefore a single pointer   indirection, which might be less efficient than direct access to a data member;   still, though, it is equivalent to a virtual function invocation.</p><h3> <a name="Heading14">Additional Overhead</a></h3><p>A pointer indirection, a <tt>type_info</tt> object, and a pointer per class   sound like a reasonable price to pay for RTTI support. This is not the full   picture, however. The <tt>type_info</tt> objects, just like any other object,   have to be constructed. Large programs that contain hundreds of distinct polymorphic   classes have to construct an equivalent number of <tt>type_info</tt> objects   as well.</p><h3> <a name="Heading15">RTTI Support Can Usually Be Toggled</a></h3><p>This overhead is imposed even if you never use RTTI in your programs. For this   reason, most compilers enable you to switch off their RTTI support (check the   user's manual to see the default RTTI setting of your compiler and how it can   be modified). If you never use RTTI in your programs, iyou can turn off your   compiler's RTTI support. The results are smaller executables and a slightly   faster code.</p><h3> <a name="Heading16">typeid Versus dynamic_cast&lt;&gt;</a></h3><p>Until now, this chapter has discussed the indirect cost of RTTI support. It   is now time to explore the cost of its direct usage -- that is, applying <tt>typeid</tt>   and <tt>dynamic_cast&lt;&gt;</tt>.</p><p>A <tt>typeid</tt> invocation is a constant time operation. It takes the same   length of time to retrieve the runtime type information of every polymorphic   object, regardless of its derivational complexity. In essence, calling <tt>typeid</tt>   is similar to invoking a virtual member function. For instance, the expression   <tt>typeid(obj)</tt> is evaluated into something similar to the following:</p><pre><tt>return *(obj-&gt;__vptr[0]); //return the type_info object whose address</tt><tt>                         // is stored at offset 0 in the virtual table of obj</tt></pre><p>Note that the pointer to a class's <tt>type_info</tt> object is stored at a   fixed offset in the virtual table (usually <tt>0, but this is implementation-dependent</tt>).</p><p>Unlike <tt>typeid</tt>, <tt>dynamic_cast&lt;&gt;</tt> is not a constant time   operation. In the expression <tt>dynamic_cast&lt;T&amp;&gt; (obj)</tt>, where   <tt>T</tt> is the target type and <tt>obj</tt> is the operand, the time that   is needed to cast the operand to the target type depends on the complexity of   the class hierarchy of <tt>obj</tt>. <tt>dynamic_cast&lt;&gt;</tt> has to traverse   the derivation tree of the <tt>obj</tt> until it has located the target object   in it. When the target is a virtual base, the dynamic cast becomes even more   complicated (albeit unavoidable, as you have seen); consequently, it takes longer   to execute. The worst case scenario is when the operand is a deeply derived   object and the target is a nonrelated class type. In this case, <tt>dynamic_cast&lt;&gt;</tt>   has to traverse the entire derivation tree of <tt>obj</tt> before it can confidently   decide that <tt>obj</tt> cannot be cast to a <tt>T</tt>. In other words, a failed   <tt>dynamic_cast&lt;&gt;</tt> is an O(<tt>n</tt>) operation, where <tt>n</tt>   is the number of base classes of the operand.</p><p>You might recall the conclusion that from a design point of view, <tt>dynamic_cast&lt;&gt;</tt>   is preferable to <tt>typeid</tt> because the former enables more flexibility   and extensibility. Notwithstanding that, the runtime overhead of <tt>typeid</tt>   can be less expensive than <tt>dynamic_cast&lt;&gt;</tt>, depending on the derivational   complexity of the entities involved.</p><h2> <a name="Heading17">Conclusions</a></h2><p>The RTTI mechanism of C++ consists of three components: operator <tt>typeid</tt>,   operator <tt>dynamic_cast&lt;&gt;</tt>, and class <tt>std::type_info</tt>. RTTI   is relatively new in C++. Some existing compilers do not support it yet. Furthermore,   compilers that support it can usually be configured to disable RTTI support.   Even when there is no explicit usage of RTTI in a program, the compiler automatically   adds the necessary "scaffolding" to the resultant executable. To avert this,   you can usually switch off your compiler's RTTI support.</p><p>From the object-oriented design point of view, operator <tt>dynamic_cast&lt;&gt;</tt>   is preferable to <tt>typeid</tt> because it enables more flexibility and robustness,   as you have seen. However, <tt>dynamic_cast&lt;&gt;</tt> can be slower than   <tt>typeid</tt> because its performance depends on the proximity of its target   and operand, as well as on the derivational complexity of the latter. When complex   derivational hierarchies are used, the incurred performance penalty might be   noticeable. It is recommended, therefore, that you use RTTI judiciously. In   many cases, a virtual member function is sufficient to achieve the necessary   polymorphic behavior. Only when virtual member functions are insufficient should   RTTI be considered.</p><p>Following are a few additional notes to keep in mind when using RTTI:</p><ul>  <li>In order to enable RTTI support, an object must have at least one virtual     member function. In addition, switch on your compiler's RTTI support (please     consult your user's manual for further information) if it isn't already on.</li>  <p></p>  <li> Make sure that your program has a <tt>catch</tt>-statement to handle <tt>std::bad_cast</tt>     exceptions whenever you are using <tt>dynamic_cast&lt;&gt;</tt> with a reference.     Note also that an attempt to dereference a null pointer in a <tt>typeid</tt>     expression, as in <tt>typeid(*p)</tt> where <tt>p</tt> is <tt>NULL</tt>, results     in a <tt>std::bad_typeid</tt> exception being thrown.</li>  <p></p>  <li> When you are using <tt>dynamic_cast&lt;&gt; </tt>with a pointer, always     check the returned value.</li></ul><CENTER><P><HR>  <A HREF="/publishers/que/series/professional/0789720221/index.htm"><img src="/publishers/que/series/professional/0789720221/button/contents.gif" WIDTH="128"HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A> <BR><BR><BR><p></P><P>&#169; <A HREF="/publishers/que/series/professional/0789720221/copy.htm">Copyright 1999</A>, Macmillan Computer Publishing. Allrights reserved.</p></CENTER></BODY></HTML>

⌨️ 快捷键说明

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