📄 ch07.htm
字号:
<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<B*> 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<></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<></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->b; // bb has an undefined value</tt><tt> cout<< "pa: " << pa << " pb: "<<pb <<endl; // pb was not properly </tt><tt> //adjusted; pa and pb are identical</tt><tt> pb = dynamic_cast<B*> (pa); //cross cast; adjust pb correctly</tt><tt> bb= pb->b; //OK, bb is true</tt><tt> cout<< "pa: "<< pa << " pb: " << pb <<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<></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 <iostream></tt><tt>using namespace std;</tt><tt>int main()</tt><tt>{</tt><tt> V *pv = new D;</tt><tt> A* pa = dynamic_cast<A*> (pv); // downcast</tt><tt> cout<< "pv: "<< pv << " pa: " << pa <<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<></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<></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<></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<></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->__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<></tt> is not a constant time operation. In the expression <tt>dynamic_cast<T&> (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<></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<></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<></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<></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<></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<></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<></tt> is preferable to <tt>typeid</tt> because it enables more flexibility and robustness, as you have seen. However, <tt>dynamic_cast<></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<></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<> </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>© <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 + -