2

This is my first C++ related programming question. I consider myself a beginner in programming even though I have dealt with C++ in some ways for about half a year. My question yields at an inheritance problem that I recently faced in my work.

I have a class NodeMaster that should handle a container (right now a map) of different child objects inherited from the same base class NBase - accessing them, change data, ...

// Base class
class NBase 
{
private:
    int m_ID;
public: 
    NBase() { m_ID = 0; };
    ~NBase() {};
...
}

// Child class
class NSampler : NBase 
{
private:
    int m_ID;
    std::string m_state;
public: 
    NSampler(std::string state) : { 
        m_ID = 1; 
        m_state = state;
    }
    ... 
}

The childs of NBase are 'registered' into the map via some arbitrary index:

#include "NBase.h"

class NodeMaster
{
private:
    std::map<int, std::shared_ptr<sbr::NBase>> m_mpNodes;
public:
    void register_node(int nIdx, std::shared_ptr<sbr::NBase> pNode)
    {
        this->m_mpNodes.insert(std::pair<int, std::shared_ptr<sbr::NBase>>(nIdx, pNode->clone()));
    }
    std::shared_ptr<sbr::NBase> get_node(int idx)
    {
        std::map<int, std::shared_ptr<sbr::NBase>>::iterator itr;
        for (itr = this->m_mpNodes.begin(); itr != this->m_mpNodes.end(); ++itr) {
            if (itr->first == idx) {
            return itr->second;
        }
    }
}

I read about this problem on SO to imporve my solution step-by-step and also read about the issue of Object Slicing. Therefore I put the objects into a std::shared_ptr to guarantee the 'polymorphic behaviour' throughout the process. So far everything is good and I can add the Child class objects to the container via

std::shared_ptr<sbr::NSampler> sampler(std::make_shared<sbr::NSampler>("SAMPLER_INIT"));  
NodeMaster nodeMater;
nodeMaster.register_node(sampler->get_ID(), sampler);
        

Now what gives me headaches is how to properly access the elements of m_mpNodes later in the code ... The nodeMaster itself is passed to another function by reference pre_process_lin(nodeMaster, m_Links) and to access the elements inside this function I expected to just write e.g. nodeMaster.get_node(1) but that does not return the inherited object 'NSampler' but rather 'NBase' ... to retrieve 'NSampler' I had to use a dynamic_cast for smart pointers:

nodeMaster.get_node(1);   // NBase
...
std::shared_ptr<sbr::NSampler> test_sampler = std::dynamic_pointer_cast<sbr::NSampler>(nodeMaster.get_node(1));    // NSampler

To get this going I had to add a copy ctor to both the NBase and the NSampler (for using the covariance feature of C++) and considering that I deal with smart pointers I read this blog post to implement the copy ctor into the classes

// Base class
class NBase 
{
private:
    int m_ID;
public: 
    NBase() { m_ID = 0; };
    ~NBase() {};
public:
    std::shared_ptr<NBase> clone() const    
    {   
        return std::shared_ptr<NBase>(this->clone_impl());
    }
private:
    virtual NBase* clone_impl() const = 0;
...
}

// Child class
class NSampler : NBase 
{
private:
    int m_ID;
    std::string m_state;
public: 
    NSampler(std::string state) : { 
        m_ID = 1; 
        m_state = state;
    }
public:
    std::shared_ptr<NSampler> clone() const
    {
        return std::shared_ptr<NSampler>(this->clone_impl());
    }
private:
    virtual NSampler* clone_impl() const override
    {
        return new NSampler(*this);
    }
...
}

With this setup I solve my problem of stacking together a bunch of inherited class objects into some container (map), but since this specific copy-ctor is called (in order to be able to add them to the container) the initial member variables (e.g. m_state) get overwritten after 're-calling' the objects from the map.

Is there a better way to wrap the process of inheritance, packing to container and unpacking without losing object data on the run due to the copy-ctor above?

kuoji96
  • 31
  • 4
  • You could use `std::variant`. I do not understand what do you want `get_node` to ideally return? Or why is dynamic cast not the solution here? – Quimby Sep 20 '22 at 16:29
  • 2
    "Therefore I put the objects into a std::shared_ptr to guarantee the 'polymorphic behaviour' throughout the process. " You need to learn what is 'polymorphic behaviour'. In short - you access them through pointer to base class through virtual functions which are declared in base class and they have different behavior depends on actual type. You do not access derived class directly. If you need to `dynamic_cast` them that usually means problems in your design, though sometimes it is used. – Slava Sep 20 '22 at 16:30
  • @Quimby Thanks for the suggestion. The `get_node` basically just retrieves the object at a specific index from the container after it was put into it. The problem is the data that was assigned to `NSample` before adding it to `m_mpNodes` gets lost when using the `pNode->clone()` in the `insert` function. So after inserting I do not get the 'real' inherited object when retrieving from 'm_mpNodes' but rather the copy without data - using `dynamic_cast` is mandatory, otherwise it does not work at all – kuoji96 Sep 20 '22 at 16:59
  • @Slava Yes i will definetely have to a) improve the design and b) get into the concept of inheritence more deeply ... somehow I thought that I could easily adapt what I understood about the simple hierarchy to some more complex stuff and the solution seemed so close ... thanks for the short intro, I will dig deeper into it – kuoji96 Sep 20 '22 at 17:03
  • @kuoji96 But why clone at all? The solution in the first half of your question is working right? – Quimby Sep 20 '22 at 17:57
  • @Quimby Because otherwise the `dynamic_cast` throws an error `source type is not polymorphic`. I tried to make four minimal examples to illustrate my way of thinking: (1) https://onlinegdb.com/UD_TWsQ8z (with object slicing); (2) https://onlinegdb.com/UPJkvSn2K (with pointers); (3) https://onlinegdb.com/UdKaNrmqze (with cloning); (4) https://onlinegdb.com/ABMAcrjKS (advanced cloning) ... suprisingly everything works fine there, but not in my source code, maybe i have to go for some bugs to find – kuoji96 Sep 21 '22 at 09:09
  • @Slava I am aware of that. Maybe i was just not precise enough in my questioning since I definitely expect to have some mistakes in the code - which may be obvious to others but not to me. I try to avoid implementing concepts into my work that I am not fully aware of, but this just kept my attention for some time and I just wanted to figure out why things do not work as expected. – kuoji96 Sep 21 '22 at 09:13
  • @Quimby Just noticed that I used `std::vector` in the examples but it should make no diff to `map` – kuoji96 Sep 21 '22 at 09:16
  • 1
    @kuoji96 `dynamic_cast` requires at least one virtual method, I did assume you have a virtual destructor in your class hiearchy, if not, you can add it and it will work. Still, I do not see the value of `clone`. – Quimby Sep 21 '22 at 09:24
  • @Quimby That does the trick! I did not specify the destructor of the parent class as `virtual` - however: doing so, it works great! The `clone` was just a workaround that somehow happen to solve the issue of the error `source type is not polymorphic`. I won't use it anymore now that the code runs – kuoji96 Sep 21 '22 at 11:21
  • @kuoji96 Yea, sorry about not saying that earlier, I thought it was hidden in `...` in your definitions. Also it is a good idea to use virtual dtors in inheritace in general (unless you really really need to avoid vtable generation) to allow delete-through-base. Making `base* b = new child; delete b;` valid because without virtual dtor, it is UB. – Quimby Sep 21 '22 at 11:30
  • @Quimby Since adding `virtual` to my destructor solves my issue I would accept your help as an answer if you like. Maybe I should edit the question to point out that my mistake was hidden in the `...` – kuoji96 Sep 21 '22 at 11:59
  • If you start your exploration of inheritance with `dynamic_cast`, your learning has taken a wrong turn somewhere. Consider `dynamic_cast` a forbidden operation until you are more comfortable with OOP concepts. – n. m. could be an AI Sep 21 '22 at 12:57

0 Answers0