Search code examples
c++c++17shared-ptrmake-shared

Returning make_shared<Base>, inheritence and casting to Derived not working as expected?


Consider this example constructing shared_ptr<T> in various ways and returning:

#include <memory>
#include <iostream>

class Base
{
public:
    virtual ~Base() {}
    Base(int y) : y_(y) {}
    int y_;
};

class Derived : public Base
{
public:
    Derived(int y, int z) : Base(y), z_(z) {}
    int z_;
};

std::shared_ptr<Base> A()
{
    return std::shared_ptr<Base>(new Derived(1, 2));
}

std::shared_ptr<Base> B()
{
    std::shared_ptr<Derived> result = std::make_shared<Derived>(Derived(1, 2));
    return result;
}

std::shared_ptr<Base> C()
{
    std::shared_ptr<Base> result = std::make_shared<Base>(Derived(1, 2));
    return result;
}

std::shared_ptr<Base> D()
{
    return std::make_shared<Base>(Derived(1, 2));
}


int main(int argc, char** argv)
{
    // Works fine...
    std::shared_ptr<Derived> resultA = std::dynamic_pointer_cast<Derived>(A());
    
    // Works fine...
    std::shared_ptr<Derived> resultB = std::dynamic_pointer_cast<Derived>(B());
    
    // Does not cast to base? ...
    std::shared_ptr<Derived> resultC = std::dynamic_pointer_cast<Derived>(C());

    // Object returns fine (of type Base), but cannot be cast to Derived?
    std::shared_ptr<Base> resultCBase = C();
    std::shared_ptr<Derived> resultCDerived = std::dynamic_pointer_cast<Derived>(resultCBase);
    
    // Does not cast to derived...
    std::shared_ptr<Derived> resultD = std::dynamic_pointer_cast<Derived>(D());

    return 0;
}

In summary:

Returning std::make_shared<T> seems to work fine and allows caller to correctly cast. (see A()).

Using make_shared<Derived> to create a Derived, and then relying on implicit casting to return a shared_ptr<Base> works and allows caller to correctly cast. (see B()).

However for C() and D() when using make_shared<Base>(Derived(...)), shared_ptr<Base> is constructed (seems correct) but cannot cast to std::shared_ptr<Derived>?

I'm not familiar with what make_shared<T> gives (Although other answers of SO allude to better type safety and single allocation?), however it doesn't seem to perform or behave in the same manner as when replacing with std::shared_ptr<T>(new T(...))?

Could someone please explain to me what is happening here and why it does not work as I expect (I presume I am using it wrong, or there is some subtle behaviour trait that I should know when using it)?

Since the above example has discrepancies, A() & B() working, but not C() and D() (assuming I am using it correctly)... why is make_shared<T> recommended over std::shared_ptr<T>(new T(...)), and are there any exceptions which would mean it is not recommended over the other?


Solution

  • I'm not familiar with what make_shared<T> gives

    It creates a T (on the heap, wrapping it in a shared_ptr). It creates exactly and only T. Your make_shared<Base> calls are equivalent to new Base(Derived(1, 2). This will construct a Base (by copying from the given Derived's Base subobject, commonly called "slicing", since you're only copying part of an object), because that's the type you gave it.

    There is no Derived in those shared_ptrs, so you cannot dynamic_pointer_cast to that type.

    however it doesn't seem to perform or behave in the same manner as when replacing with std::shared_ptr<T>(new T(...))

    why is make_shared<T> recommended over std::shared_ptr<T>(new T(...)),

    std::shared_ptr<Base>(new Base(Derived(1, 2))) would have had the exact same problem. This has nothing to do with make_shared and everything to do with you creating the wrong type.