0

I am having trouble understanding casting of shared_ptr. This thread does a pretty good job explaining the behavior with normal pointers, and the result is very intuitive - exactly what I would expect. However, shared_ptr show different results - I created 3 classes, one base and two derived and played around a bit with using ***_ptr_cast:

#include <iostream>
#include <memory>

using namespace std;

struct Base 
{
    virtual void f() { cout << "Base" << endl; }
    
    string name = "Base";
};

struct FirstDerived : public Base
{
    void f() override { cout << "FirstDerived" << endl; }
    
    void firstDerived() { cout << "FirstDerived only method" << endl; }
    
    string name = "FirstDerived";
};

struct SecondDerived : public Base
{
    void f() override { cout << "SecondDerived" << endl; }
    
    void secondDerived() { cout << "SecondDerived only method" << endl; }
    
    string name = "SecondDerived";
};


int main()
{
    FirstDerived fd;
    std::shared_ptr<Base> bf = make_shared<Base>(fd);
    std::shared_ptr<FirstDerived> fdp = std::static_pointer_cast<FirstDerived>(bf);
    
    if (fdp)
    {
        fdp.get()->f();
        fdp.get()->firstDerived();
        //cout << fdp.get()->name;
    }
    
    FirstDerived sd;
    std::shared_ptr<Base> bs = make_shared<Base>(sd);
    std::shared_ptr<SecondDerived> sdp = std::static_pointer_cast<SecondDerived>(bs);
    
    if (sdp)
    {
        sdp.get()->f();
        sdp.get()->secondDerived();
        //cout << sdp.get()->name;
    }
   
   return 0;
}

The output for this program shows (funnily cout << fdp.get()->name; is not possible because it will segfault):

Base
FirstDerived only method
Base
SecondDerived only method

Researching a bit further, I came to the conclusion that static_cast might be the wrong cast, so I changed it to a dynamic_cast. The dynamic version however will never return a valid value, even if I am casting to the correct derived version.

The desired result would be, that only if the initial object was of the type of the one that is being cast to in the next step then there will be a result. Else it should be null.

How should I correctly do this, is there a cast-version for what I am searching?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Nestroy
  • 55
  • 1
  • 8
  • 1
    *"even if i am casting to the correct derived version"* - Yes it will. If it didn't, you were **not** pointing at an object of the correct derived version. Your pre-conditions in your test were wrong. Re-evaluate your assertions. – StoryTeller - Unslander Monica Jan 07 '21 at 08:40
  • StoryTeller is right. The problem seems to be your assumption that `make_shared(fd);` makes a copy of `fd`. It doesn't. It make a `Base` object, using a `Base` constructor which can take `fd`. That happens to be `Base::Base(base const&)`, the copy constructor, because `fd` has a `Base` subobject. – MSalters Jan 07 '21 at 09:09
  • I see, thank you. So there is absolutetly no way to achieve my wished output with the current status? – Nestroy Jan 07 '21 at 09:25
  • 1
    There's a very easy way in fact: just use `make_shared(fd)` to copy the whole `fd`. Remember that your cast is working on the shared object `*bf`, and not on the ctor arguments which were used to create `*bf`. – MSalters Jan 07 '21 at 09:32
  • As i stated below, sadly, this is not what I am searching for. I cannot change the template argument since I want to store many different derived classes of Node3d in a vector, which i suppose is not possible considering your arguments. – Nestroy Jan 07 '21 at 09:35
  • Let me explain it via [this example](https://godbolt.org/z/4n9zYM); ``FirstDerived fd;`` ``SecondDerived sd;`` ``vector vec;`` ``vec.push_back(&fd);`` ``vec.push_back(&sd);`` ``auto fdp = dynamic_cast(vec[0]);`` ``auto sdp = dynamic_cast(vec[1]);`` I would like it to work as shown in this demo usual pointers, but with smart pointers. – Nestroy Jan 07 '21 at 09:43

1 Answers1

1

You need to change the types in std::make_shared to FirstDerived and SecondDerived, respectively. Also, sd should be of type SecondDerived.

int main()
{
    FirstDerived fd;
    std::shared_ptr<Base> bf = make_shared<FirstDerived>(fd);
    std::shared_ptr<FirstDerived> fdp = std::static_pointer_cast<FirstDerived>(bf);
    
    if (fdp)
    {
        fdp.get()->f();
        fdp.get()->firstDerived();
        cout << fdp.get()->name;
    }
    
    SecondDerived sd;
    std::shared_ptr<Base> bs = make_shared<SecondDerived>(sd);
    std::shared_ptr<SecondDerived> sdp = std::static_pointer_cast<SecondDerived>(bs);
    
    if (sdp)
    {
        sdp.get()->f();
        sdp.get()->secondDerived();
        cout << sdp.get()->name;
    }
   
   return 0;
}

Here's a live demo.

EDIT As requested by OP (example application without smart pointers), he wants to add smart pointers to a vector and dynamically dispatch the stored types. To do so, one can simple create a vector std::vector<std::shared_ptr<Base>> vec and add elements via vec.push_back(std::make_shared<FirstDerived>(fd)) or vec.push_back(std::make_shared<SecondDerived>(sd)). Here is an example:

int main()
{
    FirstDerived fd;
    SecondDerived sd;

    vector<std::shared_ptr<Base>> vec;
    
    vec.push_back(std::make_shared<FirstDerived>(fd));
    vec.push_back(std::make_shared<SecondDerived>(sd));
    
    auto fdp = std::dynamic_pointer_cast<FirstDerived>(vec[0]);
    auto sdp = std::dynamic_pointer_cast<FirstDerived>(vec[1]);
    
    // true
    if (fdp)
    {
        fdp->f();
        fdp->firstDerived();
        cout << fdp->name;
    }
    
    // false
    if (sdp)
    {
        sdp->f();
        cout << sdp->name;
    }
   
   return 0;
}

This gives the desired output:

FirstDerived
FirstDerived only method
FirstDerived
StefanKssmr
  • 1,196
  • 9
  • 16
  • Sadly, this is not what I am searching for. I cannot change the template argument since I want to store many different derived classes of ``Node3d`` in a vector. – Nestroy Jan 07 '21 at 09:32
  • 2
    @Nestroy I updated my answer. Simply push ```std::make_shared(fd)``` to a ```std::vector>```. – StefanKssmr Jan 07 '21 at 11:11
  • Thank you for your work, however there is still a problem. Let me explain my use-case a bit further: I am having a configuration XML file in which many objects are described. All of the objects defined in there are derived from the base class in my case. Now as I am iterating through the XML-file, I am saving all those objects on a vector of type Base. Since this should work fully dynamic, i cannot simply change the type before compilation. – Nestroy Jan 07 '21 at 12:39
  • @Nestroy This sounds like a job for a factory pattern. – StefanKssmr Jan 07 '21 at 12:53
  • That is what I am currently doing, but after some experimentation I can clearly see, that it does not work with normal pointers too. Haha... I recreated my project in small [here](https://godbolt.org/z/c45KPx). Exceeds the scope of this post however I think. – Nestroy Jan 07 '21 at 12:56
  • 1
    @Nestroy I changed your Factory accordingly [here](https://godbolt.org/z/n8P8c6). It now uses smart pointers but you can easily adjust it to return ordinary pointers. – StefanKssmr Jan 07 '21 at 13:32
  • Awesome! Thank you so much! Do you have good source where I can read about the Factory pattern in C++? This seems really interesting – Nestroy Jan 07 '21 at 13:36
  • @Nestroy Unfortunately not. I used a similar implementation before but I didn't write down where I saw it the first time. But I am sure with little search effort you'll find good articles about it. – StefanKssmr Jan 07 '21 at 13:40