run-time type information (rtti) and casts consider classes for components and windows: class...

22
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(); };

Post on 21-Dec-2015

224 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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();

};

Page 2: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

CS 213

Fall 1998

Run-time type information (RTTI)

and casts

Page 3: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 4: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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?

Page 5: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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?

Page 6: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 7: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 8: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 9: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 10: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 11: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

dynamic_cast can be used to navigate a class hierarchy:

Window

Window_with_border

Clock

Window_with_menu

Page 12: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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

Clock

Window_with_menu

upcast

Page 13: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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

Clock

Window_with_menu

downcast

Page 14: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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

Clock

Window_with_menucrosscast

Page 15: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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:

wb

wm

Clock data

Window_with_border data

Window_with_menu data

Window data

ptr

ptr

Page 16: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 17: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 18: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 19: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 20: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 21: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.

Page 22: Run-time type information (RTTI) and casts Consider classes for components and windows: class Component {... virtual void draw() {} }; class Window: public

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.