Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Downcasting shared pointer to derived class with additional functionality - is this safe?

Consider the following outline:

class Base { /* ... */ };

class Derived : public Base
{
public:
    void AdditionalFunctionality(int i){ /* ... */ }
};

typedef std::shared_ptr<Base> pBase;
typedef std::shared_ptr<Derived> pDerived;

int main(void)
{
    std::vector<pBase> v;
    v.push_back(pBase(new Derived()));

    pDerived p1(  std::dynamic_pointer_cast<Derived>(v[0])  ); /* Copy */
    pDerived p2 = std::dynamic_pointer_cast<Derived>(v[0]);    /* Assignment */

    p1->AdditionalFunctionality(1);
    p2->AdditionalFunctionality(2);

    /* A */

    return 0;
}

Here I'm extending the base class with a derived class that adds functionality (the AdditionalFunctionality method).

First question, is this OK? I've read a lot of questions that say this is not okay and you should declare the additional functionality in the base class (often suggested as making them pure virtual methods in the base class). However, I don't want to do this. I want to extend the functionality of the base class, not just implement it differently. Is there a better solution to accomplish this goal?

Okay, so in this code I am also using an STL container to store these pointers which allows me to store pointers to both objects of type Base as well as objects of type Derived without slicing the objects.

Second question, this makes sense, right? I am, in fact, avoiding slicing by using pointers to base class objects rather than the base class objects themselves?

If I "know" that a certain pointer is to a Derived object, I then use std::dynamic_pointer_cast to cast the smart pointer.

Third question, this compiles without warning and works, but is it safe? Valid? Will it break the reference counting aspect of shared pointers and fail to delete my objects or delete them before I expect?

Lastly, I can do this cast using either the copy constructor or via assignment as shown for p1 and p2. Is there a preferred / correct way of doing this?

Similar questions:

  • Downcasting shared_ptr<Base> to shared_ptr<Derived>? : This is very close, however the dervied class does not add additional functionality like mine does, so I'm not sure it's completely the same. Also, it uses boost::shared_ptr where I'm using std::shared_ptr (although I understand boost donated shared_ptr to the std library, so they're likely the same).

Thank you for your help.


Edit:

One reason I ask is that I realize that the following could be done (incorrectly):

    /* Intentional Error */
    v.push_back(pBase(new Base()));
    pDerived p3( std::dynamic_pointer_cast<Derived>(v[1]) );
    p3->AdditionalFunctionality(3); /* Note 1 */

Where I attempt to downcast a pointer to a Base object to a pointer of a Derived object and then call a method that is only implemented in the Derived class. In other words, the object pointed to doesn't define (or isn't even "aware of" the method).

This is not caught by the compiler, but may cause a segfault depending on how AdditionalFunctionality is defined.

like image 980
jedwards Avatar asked Jun 08 '11 20:06

jedwards


People also ask

Why downcasting is not allowed?

Downcasting is not allowed without an explicit type cast. The reason for this restriction is that the is-a relationship is not, in most of the cases, symmetric. A derived class could add new data members, and the class member functions that used these data members wouldn't apply to the base class.

What is the use of upcasting and downcasting in c++?

It means the upcasting used to convert the reference or pointer of the derived class to a base class. Upcasting is safe casting as compare to downcasting. It allows the public inheritance that implicitly cast the reference from one class to another without an explicit typecast.

Can you cast a shared pointer?

We can either cast the shared pointer directly by setting the type to the DerivedClass, or just use the raw points with “. get()” and static_cast in the second approach (direct cast).


2 Answers

Does the Base has a virtual destructor? If yes then it is safe to use downcasting. In your incorrect sample pDerived should be NULL in result, so you need to check the result of dynamic_pointer_cast every time.

like image 173
Kirill V. Lyadvinsky Avatar answered Oct 24 '22 15:10

Kirill V. Lyadvinsky


If the container should never have base objects in it (I can't tell from the question but that's implied by your edit) then you should make the container hold derived objects instead, and then you have automatic access to the additional function.

If the container can have both types of objects, then it seems that you want to be able to treat all the objects as the base class within that container. In this case you almost certainly want to use polymorphism to do the right thing: Have a virtual interface that basically says "Do this work" and the parent version may do nothing at all. Then the child version of the method implements the additional functionality you need.

I think you may have a code smell that your objects are less related than you think. Are you inheriting to reuse, or to allow substitution? You may also want to reconsider what your public interface looks like.

All that said, should you decide to continue with your current design (which I would at least strongly review) I think your downcasting should be safe as long as you check the result of the dynamic cast for non-null before using it.

like image 31
Mark B Avatar answered Oct 24 '22 17:10

Mark B