(Click here, next to the previous article)
Before in-depth exploration of dynamic_cast's design intent, it is worth noting that most dynamic_cast implementations are quite slow. For example, at least one common implementation is partly based on string comparisons (string comparison) for Class names (class names ). If you execute dynamic_cast on an object located at the bottom of a Single-inheritance hierarchy (single inheritance system) layer, in such an implementation, each dynamic_cast has to pay four calls to strcmp to compare class names (class names. For a hierarchy that is deeper or uses multiple inheritance (Multi-inheritance), the cost is more expensive. Some implementations work in this way for a reason (they have to do so to support dynamic linking (Dynamic Link )). Nevertheless, in addition to being alert to casts in the general sense, you should be especially alert to dynamic_casts in performance-sensitive code.
The need for dynamic_cast usually occurs in this case: You need to execute derived class operations (derived class operation) on a derived class Object (derived class Object) that you are sure to be derived ), however, you can only use one pointer-or reference-to-base (base class pointer or reference) to manipulate this object. There are two common methods to avoid this problem.
First, use the containers (container) that stores the pointer (usually smart pointer-see item 13) pointing to the derived class objects (derived class Object) directly ), this eliminates the need to manipulate this objects through base class interfaces (base class interface. For example, if only specialwindows supports blinking in our window/specialwindow inheritance system, this approach is as follows:
Class window {...};
Class specialwindow: public window {
Public:
Void blink ();
...
};
Typedef // see item 13 for info
STD: vector <STD: tr1: shared_ptr <WINDOW> vpw; // on tr1: shared_ptr
Vpw winptrs;
...
For (vpw: iterator iter = winptrs. Begin (); // undesirable code:
ITER! = Winptrs. End (); // uses dynamic_cast
++ ITER ){
If (specialwindow * psw =Dynamic_cast<Specialwindow *> (ITER-> get ()))
Psw-> blink ();
}
Try to use the following method instead:
Typedef STD: vector <STD: tr1: shared_ptr <Specialwindow>Vpsw;
VpswWinptrs;
...
For (Vpsw: Iterator iter = winptrs. Begin (); // better code: Uses
ITER! = Winptrs. End (); // No dynamic_cast
++ ITER)
(*ITER)-> Blink ();
Of course, this method does not allow you to store the derivatives pointer to all possible windows in the same container. To work with different window types, you may need multiple type-Safe Containers ).
An optional method that allows you to use a base class interface (base class Interface) to control derivatives (school biology) of all possible windows. It is in this base class (base class) provides virtual functions for you to do what you want to do ). For example, although only specialwindows can blink, declaring this function in base class (base class) and providing a default implementation that does nothing may make sense:
Class window {
Public:
Virtual void blink (){}// Default impl is no-op;
... // See item 34 for why
}; // A default impl may be
// A bad idea
Class specialwindow: public window {
Public:
Virtual void blink (){...}// In this class, blink
... // Does something
};
Typedef STD: vector <STD: tr1: shared_ptr <WINDOW> vpw;
Vpw winptrs; // container holds
// (Ptrs to) all possible
... // Window Types
For (vpw: iterator iter = winptrs. Begin ();
ITER! = Winptrs. End ();
++ ITER) // note lack
(* ITER)-> blink (); // dynamic_cast
Either way-use type-Safe Containers (Type-Safe Container) or move virtual functions (virtual functions) Up In hierarchy (Inheritance System)-is not applicable everywhere, however, in many cases, they provide feasible candidate methods other than dynamic_casting. When they are available, you should use them.
One thing you should definitely avoid is the cascade dynamic_casts design. That is to say, it looks like anything like this:
Class window {...};
... // Derived classes are defined here
Typedef STD: vector <STD: tr1: shared_ptr <WINDOW> vpw;
Vpw winptrs;
...
For (vpw: iterator iter = winptrs. Begin (); iter! = Winptrs. End (); ++ ITER)
{
If (specialwindow1 * psw1 =
Dynamic_cast<Specialwindow1 *> (ITER-> get ())){...}
Else if (specialwindow2 * psw2 =
Dynamic_cast<Specialwindow2 *> (ITER-> get ())){...}
Else if (specialwindow3 * psw3 =
Dynamic_cast<Specialwindow3 *> (ITER-> get ())){...}
...
}
Such C ++ will generate large and slow code, and it is very fragile, because every time the window class hierarchy (class inheritance system) changes, all such code must be checked to confirm whether updates are required. (For example, if you add a new derived class (derived class), you may need to add a new condition branch in the cascade above .) Code like this should always be replaced by something based on virtual function cals.
Good C ++ seldom uses casts (forced transformation), but it is usually not practical to remove it completely. For example, cast (forced transformation) on page 1 from int to double is a rational use of cast (forced transformation), although it is not absolutely necessary. (The code can be rewritten to declare a new double type variable and initialize it with the value of X .) Just like the most suspicious structure, casts (forced transformation) should be isolated as much as possible. Typically, it is hidden inside the function, and the interface of the function is used to protect the caller from the internal foul work.
Things to remember
- Avoid the use of casts (forced transformation) at any time, especially when applying dynamic_casts in performance-sensitive code. If a design requires casting (forced transformation ), try to develop a cast-free (not forced transformation) candidate solution.
- If casting is required (forced transformation), try to hide it in a function. Customers can call that function instead of adding casts to their own code (forced transformation ).
- Try to replace the old-style casts (forced transformation of the old style) with C ++-style casts (forced transformation of the old style ). They are more likely to be noticed, and what they do is clearer.