views:

238

answers:

5

If I have various subclasses of something, and an algorithm which operates on instances of those subclasses, and if the behaviour of the algorithm varies slightly depending on what particular subclass an instance is, then the most usual object-oriented way to do this is using virtual methods.

For example if the subclasses are DOM nodes, and if the algorithm is to insert a child node, that algorithm differs depending on whether the parent node is a DOM element (which can have children) or DOM text (which can't): and so the insertChildren method may be virtual (or abstract) in the DomNode base class, and implemented differently in each of the DomElement and DomText subclasses.

Another possibility is give the instances a common property, whose value can be read: for example the algorithm might read the nodeType property of the DomNode base class; or for another example, you might have different types (subclasses) of network packet, which share a common packet header, and you can read the packet header to see what type of packet it is.

I haven't used run-time-type information much, including:

  • The is and as keywords in C#
  • Downcasting
  • The Object.GetType method in dot net
  • The typeid operator in C++

When I'm adding a new algorithm which depends on the type of subclass, I tend instead to add a new virtual method to the class hierarchy.

My question is, when is it appropriate to use run-time-type information, instead of virtual functions?

+3  A: 

In C++, among some other obscure cases (which mostly deal with inferior design choices), RTTI is a way to implement so-called multi methods.

sbi
It is 'a' way, yes; an alternate way is to use http://en.wikipedia.org/wiki/Double_dispatch which uses only virtual functions: you'd add a new virtual function for each subclass of the parameter type.
ChrisW
If you look at the link I posted, you'll see that it, in fact, leads to "multiple dispatch", which is a generalization of double dispatch. `:)`
sbi
A: 

dynamic_cast<>, if I remember correctly, is depending on RTTI. Some obscure outer interfaces might also rely on RTTI when an object is passed through a void pointer (for whatever reason that might happen).

That being said, I haven't seen typeof() in the wild in 10 years of pro C++ maintenance work. (Luckily.)

DevSolar
typeof will be added with C++0x as the decltype keyword (very useful, IMO). It doesn't belong to runtime type information, being a compile-time construct and a way to make productive use of some of the information that a C++ compiler has anyway, but currently only puts to use in error messages.
UncleBens
+5  A: 

When there's no other way around. Virtual methods are always preferred but sometimes they just can't be used. There's couple of reasons why this could happen but most common one is that you don't have source code of classes you want to work with or you can't change them. This often happens when you work with legacy system or with closed source commercial library.

In .NET it might also happens that you have to load new assemblies on the fly, like plugins and you generally have no base classes but have to use something like duck typing.

vava
What's the reason for saying that RTTI is deprecated, a method of last resort?
ChrisW
@ChrisW, It's just harder to understand and much slower to execute. It is not deprecated, it's just other methods are better :)
vava
There's little reason for it to be slower: the RTTI can be stored in the class vtable, just as a virtual function pointer is. I'm not sure why it's harder to understand, either, because in a way checking RTTI is more local: for example if you see "if (foo is Foo)" then you know what's being checked, without going and looking at the definitions of the virtual functions in several subclasses.
ChrisW
Oh, and there's also maintainability issue. Code that frequently checks for class types are nightmare to change when new class is added.
vava
vava
"nightmare to change when new class is added" - That's a good argument. A reason why it's a 'nightmare' IMO is that it's relatively easy to find all the virtual functions (defined and used) in the source code, e.g. by using "Find All References" in the IDE; but it's comparatively difficult to find all places where RTTI (for example downcasts) are being used, because the IDE doesn't offer support for finding them (C++ has new-style cast syntax like `dynamic_cast` that's easy to search for, but C# doesn't).
ChrisW
As for why it is slower: VC, when having to do RTTI across DLL boundaries, falls back to string comparison.
sbi
@ChrisW, also with RTTI the code that controls the behavior of the class is taken away from the class code. It might be a good thing if a class is bloated but most of the times it is a bad thing as that code tend to be spread trough the system making it harder to understand what's going on.
vava
A: 

You can refer to More Effective C# for a case where run-time type checking is OK.

Item 3. Specialize Generic Algorithms Using Runtime Type Checking

You can easily reuse generics by simply specifying new type parameters. A new instantiation with new type parameters means a new type having similar functionality.

All this is great, because you write less code. However, sometimes being more generic means not taking advantage of a more specific, but clearly superior, algorithm. The C# language rules take this into account. All it takes is for you to recognize that your algorithm can be more efficient when the type parameters have greater capabilities, and then to write that specific code. Furthermore, creating a second generic type that specifies different constraints doesn't always work. Generic instantiations are based on the compile-time type of an object, and not the runtime type. If you fail to take that into account, you can miss possible efficiencies.

For example, suppose you write a class that provides a reverse-order enumeration on a sequence of items represented through IEnumerable<T>. In order to enumerate it backwards you may iterate it and copy items into an intermediate collection with indexer access like List<T> and than enumerate that collection using indexer access backwards. But if your original IEnumerable is IList why not take advantage of it and provide more performant way (without copying to intermediate collection) to iterate items backwards. So basically it is a special we can take advantage of but still providing the same behavior (iterating sequence backwards).

But in general you should carefully consider run-time type checking and ensure that it doesn't violate Liskov Substituion Principle.

Dzmitry Huba
+1  A: 

This constructions ("is" and "as") are very familiar for Delphi developers since event handlers usually downcast objects to a common ancestor. For example event OnClick passes the only argurment Sender: TObject regardless of the type of the object, whether it is TButton, TListBox or any other. If you want to know something more about this object you have to access it through "as", but in order to avoid an exception, you can check it with "is" before. This downcasting allows design-type binding of objects and methods that could not be possible with strict class type checking. Imagine you want to do the same thing if the user clicks Button or ListBox, but if they provide us with different prototypes of functions, it could not be possible to bind them to the same procedure.

In more general case, an object can call a function that notifies that the object for example has changed. But in advance it leaves the destination the possibility to know him "personally" (through as and is), but not necessarily. It does this by passing self as a most common ancestor of all objects (TObject in Delphi case)

Maksee
Yes, event handlers are a good example. More generally, any time your application wants to store a pointer to an application type, using framework code which doesn't know your application types; this includes for example using the `object Tag` property of many of the dot net framework types, and the `void* lpParameter` passed to the Win32 `CreateThread` function. You can store it easily enough, but then you need to downcast when you get it back.
ChrisW
@ChrisW: I don't think you can `dynamic_cast` from a `void*` (at least not in C++). How is the compiler to know there's an object of polymorphic type at the address pointed to and what its internal data's layout is?
sbi