C++ static_cast and dynamic_cast of polymorphic classes using unique_ptr

750 views Asked by At

I was practicing static_cast and dynamic_cast on polymorphic classes in C++. I tried it using both raw pointers and unique_ptr. While the former doesn't create problems, the later does. Here I present my code:-

#include <iostream>
#include <memory>
#include <exception>
#include <stdexcept>
using namespace std;

class A
{
    int a, id=0;
    static int i;
public:
    A()
    {
        id=++i;
        cout<<"constructing A: "<<id<<"\n";
    }
    virtual void get()
    {
        cout<<"enter a: ";
        cin>>a;
    }
    virtual void disp()
    {
        cout<<"a = "<<a<<"\n";
    }
    virtual ~A()
    {
        cout<<"destroying A: "<<id<<"\n";
    }
};
int A::i=0;
class B: public A
{
    int b;
public:
    B()
    {
        cout<<"constructing B\n";
    }
    void get()
    {
        cout<<"enter b: ";
        cin>>b;
    }
    void disp()
    {
        cout<<"b = "<<b<<"\n";
    }
    ~B()
    {
        cout<<"destroying B\n";
    }
};

void show (unique_ptr<B> &p)
{
    p->get();
    p->disp();
}
void d_cast (unique_ptr<A> &pa)
{
    unique_ptr<B> pb;
    try
    {
        pb.reset(dynamic_cast<B*>(pa.release()));
        if (pb==nullptr)
            throw runtime_error {"nullptr exception"};
        show(pb);
        cout<<"dynamic_cast successful\n\n";
    }
    catch (exception &e)
    {
        cout<<"dynamic_cast unsuccessful: "<<e.what()<<"\n\n";
    }
    pa.reset(pb.release());
}
void s_cast (unique_ptr<A> &pa)
{
    unique_ptr<B> pb;
    try
    {
        pb.reset(static_cast<B*>(pa.release()));
        if (pb==nullptr)
            throw runtime_error {"nullptr exception"};
        show(pb);
        cout<<"static_cast successful\n\n";
    }
    catch (exception &e)
    {
        cout<<"static_cast unsuccessful: "<<e.what()<<"\n\n";
    }
    pa.reset(pb.release());
}

int main()
{
    cout<<R"(using "unique_ptr<A> pa with new A" :-)"<<"\n\n";
    unique_ptr<A> pa(new A);    // (1)
    d_cast(pa);
    s_cast(pa);      // (2)
    cout<<"\n"<<R"(using "unique_ptr<A> pa with new B" :-)"<<"\n\n";
    pa.reset(new B);
    d_cast(pa);
    s_cast(pa);
    return 0;
}

The output of the code is:-

using "unique_ptr<A> pa with new A" :-

constructing A: 1
dynamic_cast unsuccessful: nullptr exception

static_cast unsuccessful: nullptr exception


using "unique_ptr<A> pa with new B" :-

constructing A: 2
constructing B
enter b: 7
b = 7
dynamic_cast successful

enter b: 8
b = 8
static_cast successful

destroying B
destroying A: 2

I have only 2 questions as I have marked already:-

  1. Why isn't the first object {denoted by (1)} destroyed whereas the one called with "new B" is destroyed ?

  2. Why is (2) throwing the exception ? Interestingly if i reverse the positioning of s_cast(pa) and d_cast(pa) then (2) doesn't throw any exception and works fine (problem (1) still persists however).

1

There are 1 answers

1
Anwesha On BEST ANSWER

Ok ! So you need to change your function d_cast function definition like this:-

void d_cast (unique_ptr<A> &pa)
{
unique_ptr<B> pb;
A *aptr=pa.release();   // make a pointer of type A
try
{
    pb.reset(dynamic_cast<B*>(aptr));   // assign aptr instead of pa.release() here
    if (pb==nullptr)
    throw runtime_error {"nullptr exception"};
    show(pb);
    cout<<"dynamic_cast successful\n\n";
    pa.reset(pb.release());   // reset pa with pb.release() and not with aptr becomes pb has the ownership of aptr
}
catch (exception &e)
{
    cout<<"dynamic_cast unsuccessful: "<<e.what()<<"\n\n";
    pa.reset(aptr);    // reset aptr back to pa as pb holds no ownership of aptr
}
}

As you must be knowing the d_cast would fail and thus the expression of dynamic_cast<B*>(pointer_of_type_A) would return nullptr. If there would have been a reference instead of pointer then std::bad_cast exception would be thrown. But because you are using the release() function, the unique_ptr object pa gets rid of the ownership of the pointer and there is no object or pointer to trace it back. Thus you should use A *aptr to hold the released pointer and return it to pa if the casting fails.

If you do so, your both problems are solved