What is the proper use case for dynamic_cast?
Asked Answered
P

5

23

I have been told many times (and seen myself in practice) that the use of dynamic_cast often means bad design, because it can and should be replaced with virtual functions.

For example, consider the following code:

class Base{...};
class Derived:public Base{...};
...
Base* createSomeObject(); // Might create a Derived object
...
Base* obj = createSomeObject();
if(dynamic_cast<Derived*>(obj)){
 // do stuff in one way
}
else{
// do stuff in some other way
}

It can be easily seen that instead of writing dynamic casts we can just add a virtual function doStuff() to Base and re-implement it in Derived.

In that case, my question is, why do we have dynamic_cast in the language at all? Is there an example in which the use of dynamic_cast is justified?

Pueblo answered 25/4, 2011 at 8:53 Comment(0)
H
8

It can be easily seen that instead of writing dynamic casts we can just add a virtual function doStuff() to Base and re-implement it in Derived.

YES. That is what virtual functions are for.

class Base
{
  public:
      virtual void doStuff();
};
class Derived: public Base
{
  public:
      virtual void doStuff(); //override base implementation
};

Base* createSomeObject(); // Might create a Derived object

Base* obj = createSomeObject();
obj->doStuff(); //might call Base::doStuff() or Derived::doStuff(), depending on the dynamic type of obj;

Did you notice how virtual function eliminates dynamic_cast?

Use of dynamic_cast usually indicates that you cannot acheive your goal using common interface (i.e virtual functions), hence you need to cast it to exact type, so as to call the specific member functions of type base/derived classes.

Hurlow answered 25/4, 2011 at 8:58 Comment(0)
P
32

The trouble with virtual functions is that all classes in the hierarchy must have an implementation or be abstract, and that's definitely not always the right thing to do. For example, what if Base is an interface, and in the if, you need to access the internal implementation details of Derived? That's certainly not doable in a virtual function. In addition, dynamic_cast is needed for both upcasting and downcasting in certain multiple inheritance situations. And there are limits as to what can be done in virtual functions- for example, templates. And finally, sometimes you need to store a Derived*, not just call a function on it.

Essentially, virtual functions only work in some cases, not all of them.

Paolo answered 25/4, 2011 at 9:6 Comment(0)
M
21

I think there are two cases where using dynamic_cast is a valid thing to do. The first is to check if an object supports an interface, and the second is to break encapsulation. Let me explain both in detail.

Checking for an Interface

Consider the following function:

void DoStuffToObject( Object * obj )
{
    ITransactionControl * transaction = dynamic_cast<ITransactionControl>( obj );

    if( transaction )
        transaction->Begin();

    obj->DoStuff();

    if( transaction )
        transaction->Commit();
}

(ITransactionControl would be a pure abstract class.) In this function, we want to "DoStuff" in the context of a transaction if the object supports transaction semantics. If it doesn't, it's fine to just go ahead anyway.

Now we certainly could just add virtual Begin() and Commit() methods to the Object class, but then every class that derives from Object gets Begin() and Commit() methods, even if they have no awareness of transactions. Use of virtual methods in the base class simply pollutes its interface in this case. The example above promotes better adherance to both the single responsibility principle and the interface segregation principle.

Breaking Encapsulation

This may seem like strange advice considering that dynamic_cast is generally considered harmful because it allows you to break encapsulation. However, done correctly, this can be a perfectly safe and powerful technique. Consider the following function:

std::vector<int> CopyElements( IIterator * iterator )
{
   std::vector<int> result;

   while( iterator->MoveNext() )
       result.push_back( iterator->GetCurrent() );

   return result;
}

There's nothing wrong here. But now suppose that you start seeing performance problems in the field. After analyzing, you find that your program is spending an auwful lot of time inside this function. The push_backs result in multiple memory allocations. Even worse, it turns out that "iterator" is almost always an "ArrayIterator". If only you were able to make that assumption, then your performance problems would disappear. With dynamic_cast, you can do exactly that:

 std::vector<int> CopyElements( IIterator * iterator )
{
   ArrayIterator * arrayIterator = dynamic_cast<ArrayIterator *>( iterator );

   if( arrayIterator ) {
       return std::vector<int>( arrayIterator->Begin(), arrayIterator->End() );
   } else {
       std::vector<int> result;

       while( iterator->MoveNext() )
           result.push_back( iterator->GetCurrent() );

       return result;
   }
}

Once again, we could add a virtual "CopyElements" method to the IIterator class, but this has the same drawbacks I mentioned above. Namely, it bloats the interface. It forces all implementors to have a CopyElements method, even though ArrayIterator is the only class that will do something interesting in it.

All that being said, I recommend using these techniques sparingly. dynamic_cast is not free and is open to abuse. (And frankly, I've seen it abused far more often than I've seen it used well.) If you find yourself using it a lot, it's a good idea to consider other approaches.

Malachite answered 28/11, 2012 at 17:9 Comment(3)
Not sure I'm a fan of this.. seems to me that in the CopyElements that the IIterator should have a a virtual ToVector or something that does this polymorphicly.Prototype
Yes, that is another approach. But I would argue that its leads to pollution of your interfaces. In general, we want to keep interfaces small and cohesive (interface segregation principle). It can also involve quite a bit of work in an existing code base. Adding a "CopyTo" method may seem attractive with a trivial example like this, but what if there are 20 existing implementations?Malachite
@PeterRuderman Quite a solid point about bloating one's interface. People rarely think about that these daysDexterdexterity
H
8

It can be easily seen that instead of writing dynamic casts we can just add a virtual function doStuff() to Base and re-implement it in Derived.

YES. That is what virtual functions are for.

class Base
{
  public:
      virtual void doStuff();
};
class Derived: public Base
{
  public:
      virtual void doStuff(); //override base implementation
};

Base* createSomeObject(); // Might create a Derived object

Base* obj = createSomeObject();
obj->doStuff(); //might call Base::doStuff() or Derived::doStuff(), depending on the dynamic type of obj;

Did you notice how virtual function eliminates dynamic_cast?

Use of dynamic_cast usually indicates that you cannot acheive your goal using common interface (i.e virtual functions), hence you need to cast it to exact type, so as to call the specific member functions of type base/derived classes.

Hurlow answered 25/4, 2011 at 8:58 Comment(0)
T
0

The subclass may have other methods not present in the base class, and that may not make sense in the context of the other subclasses. But generally you should avoid it.

Talanian answered 25/4, 2011 at 8:57 Comment(0)
O
0

What if, you have a method (call it foo) which receives BaseClass*, and it's expended for DerivedClass*. If I'll write:

BaseClass* x = new DerivedClass();

and call foo with x, I'll get to foo (BaseClass varName), and not foo (DerivedClass varName).

One solution is to use dynamic_cast and test it for against NULL, and if it's not null, call foo with the casted var and not x.

It's not the most object oriented situation, but it happens, and dynamic_cast can help you with it (well, casting in general is not too object oriented).

Otto answered 25/4, 2011 at 9:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.