4

When is dynamic_cast from parent to child useful in practice? Is this always a bad practice?

This is NOT a duplicate of: C++ Polymorphism: from parent class to child Run-time type information in C++

First, let's start with a code example and then let's draw some conclusions.

#include<iostream> 
using namespace std; 
class B { public: virtual void fun() {cout<<"!class B!\n";} }; 
class D: public B { public: void fun() {cout<<"!class D!\n";} }; 

int main() 
{ 
/////////   1. Dynamic initialization parent from child

B *bAux = new D; 
D *d = dynamic_cast<D*>(bAux); 
cout<<"1.1. for d is "; d->fun();   //class D

B *b = dynamic_cast<B*>(d);
cout<<"1.2. for b is "; b->fun();   //class D

/////////   2. Dynamic initialization child from parent

//D *dAux4 = new B;  //invalid conversion from ‘B*’ to ‘D*’

/////////   3. Casting the parent to child

B *bAux2 = new B;
cout<<"3.1. for bAux2 is "; bAux2->fun();   //class B

D *d2 = dynamic_cast<D*>(bAux2); 
if (d2 != NULL){ cout<<"3.2. for d2 is "; d2->fun(); }  //cannot cast parent to child
else cout<<"3.2. cannot cast B* to D* \n";

/////////   4. Casting the child to parent

D *dAux3 = new D;
cout<<"4.1. for dAux3 is "; dAux3->fun();   //class D

B *b3 = dynamic_cast<B*>(dAux3); 
cout<<"4.2. for b3 is "; b3->fun();   //class D

getchar(); 
return 0; 
} 

Second, let's draw the conclusions. The output is:

1.1. for d is !class D!                                                                                                                      
1.2. for b is !class D!                                                                                                                      
3.1. for bAux2 is !class B!                                                                                                                  
3.2. cannot cast B* to D*                                                                                                                    
4.1. for dAux3 is !class D!                                                                                                                  
4.2. for b3 is !class D!

But for case 3, we actually try to dynamic cast from B* to D* (as mentioned, not possible). I wonder in which cases is this useful? You can break the cast typing (as a parallel, in the case of static type checking, there is the well-known problem: Since a Bear is an Animal, shouldn't a set of Bears be a set of Animals? No, because then you can insert a Wolf into the set and have a Wolf among the Bears).

So, the question is, in which case is the dynamic cast from parent to child useful? Is this a bad practice?

EDIT: Following the discussion, a particular case:

/////////   5. Try to cast the parent initialized from a child to another child

B *bAux = new D; 
F *f = dynamic_cast<F*>(bAux); 
if(f == NULL)
    cout<<"5.1. Cannot cast the parent initialized from a child to another child \n";// f->fun();   //segmentation fault

B *b = dynamic_cast<B*>(f);
if(b == NULL)
    cout<<"5.2. Cannot cast the NULL pointer back to parent \n"; //b->fun();   //segmentation fault
user3742309
  • 183
  • 2
  • 12
  • 1
    I always try to avoid it, this can lead to accidentally access to non existing members in the parent B and defined in the child D. – kaileena Jan 11 '21 at 16:32
  • Yes, that can happen as well, though the compiler helps us. – user3742309 Jan 11 '21 at 16:34
  • 2
    @kaileena the whole point of `dynamic_cast` is that it will fail if the object isn't actually of type child D. You won't be able to access those non existing members. – Mark Ransom Jan 11 '21 at 16:38
  • 1
    "So, the question is, in which case is the dynamic cast from parent to child useful? Is this a bad practice?" The acyclic visitor pattern requires that for instance but in a quite generic way, not misused as a switch-case replacement. – Secundi Jan 11 '21 at 16:40
  • @kaileena What you meant are the possible drawbacks of an according static_cast, not a dynamic_cast! – Secundi Jan 11 '21 at 16:43
  • 1
    Maybe I'm reading it wrong, but you say "But for case 3, we can actually dynamic cast from B* to D*", but your code doesn't show that. It outputs `3.2. cannot cast B* to D*` – Kevin Jan 11 '21 at 16:48
  • @Kevin - it outputs 3.2. cannot cast B* to D*, as I show in the question – user3742309 Jan 11 '21 at 17:08
  • @user3742309 which disagrees with your statement, "But for case 3, we can actually dynamic cast from B* to D*", doesn't it? – Kevin Jan 11 '21 at 17:16
  • I edit the question, should be more clear now – user3742309 Jan 11 '21 at 17:29

2 Answers2

1

dynamic_cast can be useful for the case you want to get derived objects interface: if there is f() in derived and no f() function in base than you can't call f() via B*. but you can dynamic_cast to D* and call f() (if B* points to D object). (Remembering that dynamic_cast is for polymorphic types) Consider the following code:

class B {
public:
    virtual void g()
    {
        std::cout << "Base::g()\n";
    }
};
class D : public B
{
public:
    void f()
    {
        std::cout << "f()\n";
    }
    void g()
    {
        std::cout << "Derived::g()\n";
    }
};
int main()
{
    B* p = new D;
    //p->f(); // class B has no member f
    D* pd = dynamic_cast<D*>(p);
    pd->f(); // ok f() will be called

    return 0;
}
NarekMeta
  • 103
  • 9
  • Good example used. From the architectural point of view, if B has no f() function defined, most probably the intention is that a B object should have the f() functionality. This can lead to functional undefined behavior. Technically I showed it is possible, indeed. – user3742309 Jan 12 '21 at 11:39
  • For example, Circle derived from Figure. Why I would cast a Figure object to a Circle one, if order to calculate the circle surface? That Figure object can be misinterpreted or misused afterwards. – user3742309 Jan 12 '21 at 11:47
  • Hi, Here is a realistic example where you should cast to child: You have "Vehicle" base and may derived classes("Mercedes", "BMW", etc.) also derived class "Tesla". Tesla class may have a function: setAutoDriveMode() and if you have Vehicle* p = new Tesla{}; you should cast p to T* in order to call setAutoDriveMode(). You shouldn`t add setAutoDriveMode() in base: Vehicle, because not all Vehicles have that functionality. – NarekMeta Jan 13 '21 at 01:52
  • 1
    dynamic_cast is helpful in the following cases: . you cannot add virtual function to the base class because base class is from a library. . you need access to something that is derived-class specific . You don't have option to add that virtual function to base class, because the other derived classes needn't and don't have that functionality. – NarekMeta Jan 13 '21 at 01:52
  • Thanks for the answer. 1 regarding he Car e.g.: I see a bad choice of using "Vehicle* p = new Tesla{};" instead of creating directly "Tesla* p = new Tesla()"; Because you might try to cast Vehicle* p to BMW and then use a BMW specific method for a Tesla intended car. Hopefully, this does not work in C++. Here the segmentation fault helps us, I will update the question 2. Regarding the class library, I can see this case indeed, where you have this architecture constraints – user3742309 Jan 14 '21 at 18:55
0

One example is when there are multiple base classes of the same type. Only dynamic_cast can downcast from a non-unique base class to the most derived:

struct A { virtual ~A() = 0; };
struct B1 : A {};
struct B2 : A {};
struct C : B1, B2 {};
C& f(A& a) { return dynamic_cast<C&>(a); } // Only dynamic_cast can cast A& to C&.

Another example is that only dynamic_cast<void*> can return a pointer to the address of the complete object, no other cast can do.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271