240 likes | 977 Views
Run-time type information (RTTI) and casts. Consider classes for components and windows: class Component { ... virtual void draw() {} }; class Window: public Component { ... virtual void addComponent(Component *c); virtual list<Component*> getAllComponents(); };.
E N D
Run-time type information (RTTI) and casts • Consider classes for components and windows: • class Component { • ... • virtual void draw() {} • }; • class Window: public Component { • ... • virtual void addComponent(Component *c); • virtual list<Component*> getAllComponents(); • };
CS 213 • Fall 1998 • Run-time type information (RTTI) • and casts
class Component {... virtual void draw() {}}; • class Window: public Component { • ... • virtual void addComponent(Component *c); • virtual list<Component*> getAllComponents(); • }; • class Border: public Component { ... }; • class Menu: public Component { ... }; • class CheckBox: public Component { • ... • virtual void resetToDefault(); • }; • Suppose we want to call resetToDefault for all the CheckBoxes in a window. We can use getAllComponents to find all the components, and then we can loop through the components to reset each CheckBox.
class Window: public Component { • ... • virtual void addComponent(Component *c); • virtual list<Component*> getAllComponents(); • }; • void resetAllCheckBoxes(Window *w) { • list<Component*> l = w->getAllComponents(); • for(list<Component*>::iterator i = l.begin(); • i != l.end(); i++) { • Component *c = *i; • ... // is c a CheckBox??? • } • } • But what do we do for each component c? If c is a CheckBox, we’d like to call resetToDefault. But how do we know whether c is a CheckBox?
First, let’s try to avoid the problem. Maybe all components should have a resetToDefault function in them: • class Component { • ... • virtual void draw() {} • virtual void resetToDefault(); • }; • So then we can call resetToDefault for each component in the list. • But this is lousy - why should, say, a Border have a resetToDefault function? What if Component was written by a different company than CheckBox? Why should the authors of Component have to be aware that some derived classes will have a resetToDefault function?
perhaps Window could be a template class: • template <class T> • class Window: public Component { • ... • virtual void addComponent(T *c); • virtual list<T*> getAllComponents(); • }; • So if we create a Window<CheckBox>, then we know every component in the window supports the resetToDefault function. • But this isn’t appropriate here - a Window<CheckBox> can only support CheckBoxes, and not Menus or Borders. So this is too restrictive.
The two approaches just described avoid the need for determining types at run-time: • Add more functions to the base class: if every component supports resetToDefault, then we don’t have to figure out which components are CheckBoxes and which aren’t. We can just call resetToDefault for every component in the window. • Use templates: if every component in the Window is a CheckBox, then every component must support resetToDefault • In general, these are the preferred technique to use C++. • But sometimes, neither of these techniques is adequate. This is especially true when we hand an object (like a CheckBox) to a system that someone else wrote (like Window), and this system hands our object back to us with less type information. In this case, we lost the exact type of the CheckBox, and we know only that it is a Component.
To deal with this, C++ adds a special type of cast that checks the type of an object at run-time: • ... • Component *c = ...; • CheckBox *checkBox = dynamic_cast<CheckBox*>(c); • if(checkBox != NULL) checkBox->resetToDefault(); • dynamic_cast<CheckBox*>(c) examines the type of c at run-time, and if c is a pointer to a CheckBox (or if c is a pointer to an object of a type derived from CheckBox), then the cast returns a pointer to the CheckBox. • Otherwise, the cast returns NULL, indicating that c does not point to a CheckBox.
Note that dynamic_cast is very different from a C-style cast: • Component *c = ...; • CheckBox *checkBox = (CheckBox*)(c); // C-style cast • checkBox->resetToDefault(); • The C-style cast assumes that c points to a CheckBox, whether it actually does or not! No run-time check is performed. So if c points to a Border or Menu, the above code will probably crash.
C++ defines four different cast operators: • dynamic_cast • static_cast • reinterpret_cast • const_cast • dynamic_cast is the safest of these. It can only be used on classes that have virtual functions. It cannot be used, for instance, to cast a double to an int.
dynamic_cast can be used to navigate a class hierarchy: Window Window_with_border Window_with_menu Clock
dynamic_cast can be used to navigate a class hierarchy: • An upcast requires no explicit cast: • Clock *c = ...; • Window_with_border *w = c; // No cast needed • This is because all Clocks are Window_with_borders. Window Window_with_border Window_with_menu Clock upcast
dynamic_cast can be used to navigate a class hierarchy: • A downcast requires an explicit cast: • Window_with_border *w = ... • Clock *c = dynamic_cast<Clock*>(w); // Explicit cast needed • This is because not all Window_with_borders are Clocks. Window Window_with_border Window_with_menu Clock downcast
dynamic_cast can be used to navigate a class hierarchy: • A crosscast also requires an explicit cast: • Window_with_border *wb = ... • Window_with_menu *wm = • dynamic_cast<Window_with_menu*>(wb);//Explicit cast needed • This is because not all Window_with_borders are Window_with_menus. Window Window_with_border Window_with_menu crosscast Clock
Upcasts, downcasts, and crosscasts can change the address that a pointer points to. For instance, after the crosscast, wm and wb point to different locations within a single Clock object: Clock data Window data wb Window_with_border data ptr wm Window_with_menu data ptr
dynamic_casts on pointers return NULL if the cast fails: • Component *c = ...; • CheckBox *checkBox = dynamic_cast<CheckBox*>(c); • if(checkBox != NULL) checkBox->resetToDefault(); • dynamic_casts can also be used on references: • Component &c = ...; • CheckBox &checkBox = dynamic_cast<CheckBox&>(c); • If a dynamic_cast fails on a reference, a bad_cast exception is thrown.
static_cast isn’t as safe as dynamic_cast. It can do downcasts if you are absolutely sure the cast is correct: • Window_with_border *w = ... • Clock *c = static_cast<Clock*>(w); • However, no run-time check is performed, so this may lead to a crash if w isn’t really a Clock. It’s safer to use dynamic_cast. • static_cast cannot be used for crosscasts.
So why would anyone use static_cast? • It’s somewhat faster, since no run-time check is performed. (But this shouldn’t be your major concern, since you don’t use casts very often anyway.) • It can also be used to cast from void*, which is useful when interfacing to old C code: • void *v = ...; • Clock *c = static_cast<Clock*>(v); • dynamic_cast can’t be used here, since void* isn’t necessarily a pointer to a class with virtual functions.
reinterpret_cast is used for low-level bits hacking, such as converting a pointer to an integer: • Component *c = ... • int i = reinterpret_cast<int> c; • This is highly implementation dependent, and very dangerous.
Finally, const_cast is used to ignore the constness of a value: • const int *i = ...; • int *k = const_cast<int*> • This isn’t very tasteful, but is sometimes necessary when interfacing const-aware code with const-unaware code.
You should use casts rarely. If you do need to use a cast, remember that some casts are safer than others: • const_cast: safe (not necessarily tasteful, but sometimes necessary) • dynamic_cast: safe, as long as you check the result to see if it is equal to NULL. • static_cast: dangerous – needed for cast from void*, otherwise dynamic_cast is usually better • reinterpret_cast: really dangerous, needed only for low-level bits hacking • The most dangerous of all is the C-style cast, which is deprecated in C++. A C-style cast is an unpredictable mixture of const_cast, static_cast, and reinterpret_cast.
dynamic_cast used run-time type information about an object. This run-time information is also available to programmers directly. The typeid operator returns a type_info object for an expression or type: • type_info &ti = typeid(e); • type_info contains a name function to print the name of a type. This can be useful for diagnostics: • Component *c; • cout << typeid(*c).name() << endl; • Like dynamic_cast, typeid only works for classes with virtual functions.