-
Virtual Base Classes
I have a 20 year old, 100,000 line C program that I am trying to slowly convert to C++. Part of the code has a virtual base class with a large number of derived classes.
Question 1: For each derived class, I want to have a function that takes a pointer to the base class and returns a boolean indicating if the pointer dereferences to that derived class. Right now I use a virtual function for every single derived class, which means that every time I add a new derived class, I have to edit every other derived class include file and put in a funtion that returns false. This can't be the right way to do this!
Question 2: Say I have a function that takes a pointer particular derived class (bob) as an argument, or is a member function of class bob. Is there an elegant way to call this function on a pointer to the base class, assuming I have already checked that the pointer is really from the correct derived class? Right now I do a cast:
Code:
function( (bob*) pointer ) ;
( (bob*) pointer )->function() ;
This is pretty ugly. There must be a better way!
Thanks for any tips,
Brian
-
You can use a dynamic_cast to test whether a base class pointer actually points to a specific derived class object (or one of its derived classes).
Code:
bool Derived::TestPointer(Base* ptr)
{
return dynamic_cast<Derived*>(ptr) != 0;
}
You would have to add just this one virtual function to all derived classes, but it would only be the one function, not separate functions with different names for each derived class type. Also note that this will return true if the pointer points to a class derived from Derived or its children.
For question 2, if you have already checked that the pointer refers to the correct derived class, then a cast is the best way. I prefer a static_cast for this situation:
Code:
static_cast<bob*>(pointer)->function();
Note that both of these situations are often (but not always) a signal that your design could be improved. You don't usually want to know what the base class pointer pointer points to, you just want to use its interface and let the derived classes implement the interface appropriately. You might want to consider rethinking whether the derived classes really model an is-a relationship.
-
So just to make sure I understand, I would use TestPointer like this:
Code:
if( bob::TestPointer( pointer ) ) ...
As far as the casts, the relationships between the classes all make good sense. The problem is 100,000 lines of C, which originally used a tagged union of structs. I hope to get rid of most of the casts someday, but one thing at a time.
Thanks!
Brian
-
>> So just to make sure I understand, I would use TestPointer like this:
Actually, that is correct. I made a mistake when I said it should be virtual, instead it should be static. You don't actually need a function, you can just use the code directly:
Code:
bob* derived_ptr = dynamic_cast<bob*>(pointer);
if (derived_ptr)
{
derived_ptr->function(); // derived_ptr is already a bob* here, non need for static_cast.
}
-
Wonderful! I read the section in my C++ book on dynamic_cast and static_cast five times and could make no sense of it. Now it makes sense.
Brian
-
You can even do it this way:
Code:
if(bob *derived_ptr = dynamic_cast<bob*>(pointer)) {
// use derived_ptr here
}
// derived_ptr is not visible here, so you can't accidentally use it.
-
The above bit of code now occurs about 200 times in my code. It looks *much* cleaner. Good suggestion!
-
>> The above bit of code now occurs about 200 times in my code.
I know you said the relationships between the classes makes sense, but that just does not sound good. It will be a maintenance headache.
Perhaps you can move the code that requires the dynamic_casting into member functions that are virtual and implemented only in the derived classes that have actual implementations.
-
I agree. That many virtual casts are indicative of a design problem. It may be a design problem inherited from the C version, but the way I see it, you should ask yourself this question: if now is not the time to change the design, when is? When the C++ version is done?
-
I agree also. Most of my C++ coding problem are due to not knowing quite how to wrap my head around C++'s style of OOP. For example, the section I am working on now is a symbolic manipulation code, with about 10 different polymorphic types (numbers, operators, arrays, etc). The Compare() function ('==', in other words) currently accounts for ten of those dynamic casts, since it is a virtual function that looks like:
Code:
bool Derived::Compare( base* B ) {
if( Derived* b = dynamic_cast<Derived*>(B) )
return this.value() == b.value() ; // can compare same types
else
return false ;
}
I am certain that there is some cute way to write this function without a cast; I just don't know what it is yet. A template would cut the number down from 10 to 1, but that doesn't change the underlying code any.
-
Actually, that is a situation I came across a few years ago and asked about on a programming forum. In this case the dynamic_cast is an appropriate and valid solution, and it is how I solved my issue as well.
If the only situations in which you are using the dynamic_cast are ones in which the functionality depends on the derived type of both operands, then you are probably ok.
-
It's called multiple dispatch and is a known issue in C++. One possible solution is the multiple dispatcher in the Loki library. See also "Modern C++ Design" by Aleksei Alexandrescu.