dynamic_cast and polymorphism (inheritance + virtual functions) in order to generalize different types under a common base-type

I'm learning about "dynamic_cast" and wondering why is useful to have this type of cast and I ended on this thread:
https://stackoverflow.com/questions/22282160/what-is-the-advantage-of-using-dynamic-cast-instead-of-conventional-polymorphism

In the answer accepted there is this first example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Movie
{
    // ...
    virtual bool IsActionMovie() const = 0; 
};

class ActionMovie : public Movie
{
    // ...
    virtual bool IsActionMovie() const { return true; }
};

class ComedyMovie : public Movie
{
    // ...
    virtual bool IsActionMovie() const { return false; }
};


void f(Movie const &movie)
{
    if (movie.IsActionMovie())
    {
        // ...
    }
}


I understand this example:
When you create an object type "ActionMovie" in the main and you give it to the free function it will return true. So we are doing down-casting.

However I have doubts with the following snipped:
1
2
3
4
5
6
7
8
9
void receiveDataInB(Class1 &object)
{
    normalHandlingForClass1AndAnySubclass(object);
    Class2 *ptr = dynamic_cast<Class2 *>(&object);
    if (ptr != 0)
    {
        additionalSpecialHandlingForClass2(*ptr);
    }
}

I understand that if an object type "Class2" is passed to the function "receiveDataInB" the pointer will be different to null and "additionalSpecialHandlingForClass2" will be executed.
But What happen with "normalHandlingForClass1AndAnySubclass"? This function is expecting an object type "Class1" and this can be different from "Class2" and therefore the definition of this function could contain functions that can't be executed on the object type "Class2"

I also have two questions about dynamic_cast:
1.- What is the purpose of dynamic_cast and when to use it in simple words?
2.- What is the advantage of down-casting using the first example "without dynamic_cast" and the second?
Thanks.
But What happen with "normalHandlingForClass1AndAnySubclass"? This function is expecting an object type "Class1" and this can be different from "Class2" and therefore the definition of this function could contain functions that can't be executed on the object type "Class2"


With polymorphism, a pointer or reference (p/r) to a derived class is a valid p/r to base class. This is a very powerful thing: we can write a small number of functions that take p/r to base class, but send it a p/r to derived class, virtual polymorphism ensures that the correct (derived class) function is called. This saves writing lots of functions for every derived class, and not having to change the interface when extra derived classes are added. Polymorphism also works with smart pointers.

Note one can only use the virtual functions in the base class, these form the interface.

So we are doing down-casting.


That's not really down-casting IMO, for the reason given above. There is no conversion of anything there, just polymorphism. The compiler implicitly checks to see if the p/r has the same base class.

AFAIK, dynamic_cast happens at run time, and it performs checks. I gather it is supposed to be used rarely, when one has a pointer to base, and needs to cast it to derived.
Last edited on
[...] This function is expecting an object type "Class1" and this can be different from "Class2" and therefore the definition of this function could contain functions that can't be executed on the object type "Class2"

No, because a Class2 is a Class1. Every operation which can be applied to a Class1 can also be applied to a Class2.

1. What is the purpose of dynamic_cast and when to use it in simple words?

C++ defines "static" and "dynamic" type. Static type is the type known to the compiler. Dynamic type is the real type that is maybe referred to by a reference or pointer to a base class. In other words, given Derived d; Base& b = d; the static type of the expression b is Base, but the dynamic type is Derived.

dynamic_cast allows you to retrieve the dynamic (or "real") type of an object through a base class reference or pointer.

Consider this abstract base class.
struct Base { virtual ~Base() = default; };
And these two derived classes which provide orthogonal functionality.
1
2
struct D0 { void foo() { std::cout << "foo\n"; } };
struct D1 { void bar() { std::cout << "bar\n"; } };

Given a collection of polymorphic references to Base, dynamic_cast (or RTTI in general) can be used to handle different derived classes specially, with no extra information about what any given pointer refers to:
1
2
3
4
5
6
7
D0 a; D1 b;
std::vector<Base*> v { &a, &b };
for (auto&& elt: v) { 
  if (auto p = dynamic_cast<D0*>(elt); p) p->foo();
  if (auto p = dynamic_cast<D1*>(elt); p) p->bar();
  // ... more derived classes follow
}

Live demo:
http://coliru.stacked-crooked.com/a/9ce2941439232368

2. What is the advantage of down-casting using the first example "without dynamic_cast" and the second?

There is no advantage. The example only serves to illustrate that the functionality provided by RTTI can be duplicated without actually writing dynamic_cast anywhere. (You can keep track of the real type with an enumeration, for instance.)

Code that relies on dynamic type should be kept to a minimum. Exhaustive listings like which appear around uses of RTTI and dynamic cast are especially important to minimize.

You might find this thread interesting - it discusses a similar problem, namely, how to handle multiple distinct objects without a whole lot of extra boilerplate (it is complicated, though):
http://www.cplusplus.com/forum/beginner/217282/
Last edited on
Lets add some to your first example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ComedyMovie : public Movie
{
    // ...
    bool HasBlood() const; // not virtual
};


void g(Movie const &movie)
{
    if ( movie.HasBlood() ) // error: Movie has no such function
    {
        // ...
    }

    ComedyMovie * cp = dynamic_cast<ComedyMovie*>( &movie );
    if ( cp )
    {
      if ( cp->HasBlood() )
      {
          // ...
      }
      else // no-blood-comedy
      {}
    }
    else // movie is not a comedy
    {}
}

dynamic_cast is required when the interface of the base class is not "complete". This should be rare.

Multiple and virtual inheritance might have some use cases too, but frankly that is rather treacherous terrain.
Thanks for your answers!

Concept of dynamic_cast understood. I think that this is spot on:
@TheIdeasMan
take p/r to base class, but send it a p/r to derived class, virtual polymorphism ensures that the correct (derived class) function is called

@mbozzi
dynamic_cast allows you to retrieve the dynamic (or "real") type of an object through a base class reference or pointer



However I have some comments on your answers:
@TheIdeasMan
Note one can only use the virtual functions in the base class, these form the interface

Then is the first example wrong????:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Movie
{
    // ...
    virtual bool IsActionMovie() const = 0; 
};

class ActionMovie : public Movie
{
    // ...
    virtual bool IsActionMovie() const { return true; }<-- virtual member function!
};

class ComedyMovie : public Movie
{
    // ...
    virtual bool IsActionMovie() const { return false; }<-- virtual member function!
};


void f(Movie const &movie)
{
    if (movie.IsActionMovie())
    {
        // ...
    }
}


@mbozzi
Class2 is a Class1
This is a statement of the fundamental design rile Liskov Substitution Principle(LSP). Isn't it?
This is also the why what @TheIdeasMan said is true:
A pointer or reference (p/r) to a derived class is a valid p/r to base class

By the way @mbozzi nice piece of code!:
1
2
3
4
5
6
7
D0 a; D1 b;
std::vector<Base*> v { &a, &b };
for (auto&& elt: v) { 
  if (auto p = dynamic_cast<D0*>(elt); p) p->foo();
  if (auto p = dynamic_cast<D1*>(elt); p) p->bar();
  // ... more derived classes follow
}

You made me work xD. So I learned also how to use the Range-based for loop.
But still have questions on this snippet:
1.- The range_declaration:
auto&& else
"&&" Is this indicating that "elt" is a reference to reference? I thought that implicitly the elements of v are already interpreted as reference so it was not necessary to write explicitly a double reference.
2.- Two input arguments in a "if" OMG!:
if (auto p = dynamic_cast<D0*>(elt); p)
What is this if doing? What is the condition?
You can keep track of the real type with an enumeration, for instance

considering real=dynamic type
Why can be useful to keep track of the dynamic type? Can you give me a short SSCCE?
You might find this thread interesting

Added to my TO DO list, thanks :).

@keskiverto
dynamic_cast is required when the interface of the base class is not "complete"

Does it mean if there is a good first design of the class hierarchy in terms of cohesion, "dynamic_cast" may be not need it?

Last edited on
I'm really sorry. I didn't sleep last night. ;)

The questionable snippet above is basically equivalent to
1
2
3
4
5
6
7
8
  for (Base* elt: v) { 
    D0* p_d0 = dynamic_cast<D0*>(elt);
    if (p_d0) p_d0->foo();
    
    D1* p_d1 = dynamic_cast<D1*>(elt); 
    if (p_d1) p_d1->bar();
    // ... more derived classes follow
  }

Demo:
http://coliru.stacked-crooked.com/a/768c8866e96709b4

elt is a reference to reference?
Good guess, but there's no such thing.
Without going into the details, the idiom (auto&& in a range-for) gets a mutable reference to the loop contents, no matter what it is - even if the range iterator returns a temporary which wouldn't bind to a plain reference. (Look up "perfect forwarding" and "move semantics" for a complete explanation.)

2.- Two input arguments in a "if" OMG!:

The statement is like the for loop:
for (init; condition; increment) loop-body
if (init; condition) conditional-body

In fact,
if (auto p = dynamic_cast<D1*>(elt); p) p->bar();
Can be written like
for (auto p = dynamic_cast<D1*>(elt); p; ) { p->bar(); break; }
Or like
{ auto p = dynamic_cast<D1*>(elt); if (p) { p->bar(); } }
Maybe the paper does a better job than my tired self:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r0.html

This feature will be new in C++17.
Last edited on
In pre-C++17 the condition can be
declaration of a single non-array variable with a brace-or-equals initializer.


Therefore,
1
2
3
4
if ( auto p = dynamic_cast<D1*>(elt) )
{
  p->bar();
}

is already valid (and has been so quite a while now).
Last edited on
Oh, I forgot about that. Thanks!
As an aside, as a strong rule of thumb, when I see a dynamic cast in some code, it means that the design is wrong and the code needs redesigning and rewriting.
Last edited on
@TheIdeasMan
Note one can only use the virtual functions in the base class, these form the interface

Then is the first example wrong????:


No, IsActionMovie() is part of the interface: it has a pure virtual function in the base class by that name. Ordinary base class virtual functions can be used too.

2.- Two input arguments in a "if" OMG!:


Here we go, you have already run into some c++17 :+) Have you managed to update your compiler yet?

With this:

for (auto&& elt: v)

The forwarding reference is the best and safest way of doing this, it copes with const, non-const, lvalues, rvalues. So it is a eeally good idiom to remember. About rvalues and lvalues:

This one is especially enlightening about where it all came from, in particular the diagrams:

http://www.stroustrup.com/terminology.pdf

http://thbecker.net/articles/rvalue_references/section_01.html

http://en.cppreference.com/w/cpp/language/value_category

cppreference is a really good site for authoritative standard compliant reference material, if you haven't already, bookmark it :+)

https://isocpp.org/faq With explanations
The rest of that site has a getting started, Tour , blog, forums etc , all of which are good.


Given the topics you are investigating, it looks like you have some prior programming experience? It is good to know what that experience is, just so we can adjust our replies with that in mind :+) There is a fair chance you have much more experience than me :+D, I am a part time back yard basher!! Btw, the other respondents to this topic have awesome amounts of knowledge & experience :+)
Note one can only use the virtual functions in the base class, these form the interface

No.

1. Derived class can introduce new virtual functions that its subclasses can redefine. Obviously one cannot call them via Base*, and would have to dynamic_cast. Possible, but as said: dubious design, unless ...

2. Herb Sutter did recommend writing the interface with non-virtual functions:
1
2
3
4
5
6
7
8
9
10
11
class Base {
public:
  void foo(); { /* code calls fooimpl() */ }
private:
  virtual void fooimpl() = 0;
};

class Derived : public Base {
private:
  virtual void fooimpl() { /* code */ }
};

There the Base dictates which parts of foo() the subclasses can specialize.
Last edited on
keskiverto wrote:
Obviously one cannot call them via Base*


Hi,

I was meaning via a function with a base pointer as a parameter, that is classic polymorphism. I should have not put the comment in a separate paragraph :+)

With regard to point 2, that is fair.

Regards :+)
Topic archived. No new replies allowed.