Cloning Derived classes from Base classes

114 views Asked by At

I have an Event base class with a DerivedEvent. I also have BaseA with DerivedA and BaseB with DerivedB classes. The B classes have A objects in them. the A objects update a queue of event pointers: std::queue<std::unique_ptr<Event>> from the B class. There can be multiple A-like objects in B. Meaning other derived classes L,M,N,... that behave somewhat similarly to A.

In my application, I need to store a history of B objects. And I need each object in the container of B objects to be able to .do_stuff() after an .update() which should update the event queue. The .update() method can be called from A,L,M,... classes that are members of B. The event queue can reset every time a new instance is re-assigned.

I have followed the cloning ideas from the following question: Copy constructor for a class with unique_ptr. Everything seems to function properly with the exception of the event queue.

Below is a minimal reproducible example:

#include <iostream>
#include <memory>
#include <queue>

class Event{
public:
    Event(int event_type): m_type{ event_type } {}

    const int get_event_type() const{ return m_type; }
    const virtual std::string print() const = 0;
    virtual ~Event() = default;
private:
    const int m_type;
};

class DerivedEvent : public Event{
public:
    DerivedEvent(): Event{0}{}

    const std::string print() const override {
        return "0 print implementation"; 
    }
};

class BaseA{
public:
    virtual bool update() = 0;
    // this is a more complicated object than int
    virtual int get_value() const = 0;
    // provide cloning ability
    auto clone() const { return std::unique_ptr<BaseA>(clone_impl()); }
    virtual ~BaseA() = default;

protected:
    virtual BaseA* clone_impl() const = 0;
};

class DerivedA : public BaseA{
public:
    DerivedA(std::queue<std::unique_ptr<Event>>& event_queue) 
    : m_event_queue{ event_queue } 
    , m_continue { true }
    , m_value { 0 }
    { 

    } 

    bool update() override {
        if(!m_continue){ return false; }                                                   
        m_value++; 
        m_event_queue.push(std::make_unique<DerivedEvent>());  
        return true;
    }

    int get_value() const override{
        return m_value;
    }
protected:
    virtual DerivedA* clone_impl() const override {
        return new DerivedA(*this);
    }
private:
    bool m_continue;
    int m_value;
    std::queue<std::unique_ptr<Event>>& m_event_queue;
};

class BaseB{
public:
    void do_stuff(){
        while(m_A->get_value() < 5){
            if(!m_A->update()){
                break;
            }
            // do stuff
            std::cout << "doing stuff with object: " << m_A->get_value() << '\n';
            // process event queue
            while(!m_event_queue.empty()){
                std::cout << "processing events\n";
                auto event{ std::move(m_event_queue.front()) };
                std::cout << "event type: " << event->get_event_type() << '\n';
                std::cout << "event print: " << event->print() << '\n';
                m_event_queue.pop();
            }
        }
        std::cout << "end of while loop\n";
        return;
    }

    auto clone() const { return std::unique_ptr<BaseB>(clone_impl()); }

    virtual ~BaseB() = default;
protected:
    std::unique_ptr<BaseA> m_A;
    std::queue<std::unique_ptr<Event>> m_event_queue;
    virtual BaseB* clone_impl() const = 0;
}; 

template<class T>
class DerivedB : public BaseB{
public:

    DerivedB()
    {
        m_A = std::make_unique<T>(m_event_queue);
    }

    // cloning
    DerivedB(DerivedB const& other)
    {
        this->m_A = other.m_A->clone();
    }
protected:
    virtual DerivedB<T>* clone_impl() const override {
        return new DerivedB<T>(*this);
    }
};

and my main function:

int main(){

    std::unique_ptr<BaseB> b;
    // assign unique ptr b to Derived B that has a Derived A member variable
    b = std::make_unique<DerivedB<DerivedA>>(); 
    // this behaves properly
    b->do_stuff();

    // same thing as before
    std::unique_ptr<BaseB> test;
    test = std::make_unique<DerivedB<DerivedA>>(); 
    // except now keep a vector of B pointers
    std::vector<std::unique_ptr<BaseB>> test_history;
    
    // this prints out the "doing stuff with object: 1,...5"
    // but it does not have any events to process
    auto test_clone { test->clone() };
    test_clone->do_stuff();


    // this time add test clone to test_history
    test_history.push_back(test->clone());
    // re-assign test from the history vector
    test = std::move(test_history.back());
    // remove the last entry from test_history
    //test_history.pop_back();
    // do stuff --- this fails with value = 2, and doesn't seem to push to m_event_queue
    test->do_stuff();

    return 0;
}

The problem must be with .clone() and m_event_queue. The goal is to be able to have the B object pulled from the vector test->do_stuff() to do the same thing as b->do_stuff()

Expected Output ( works with no cloning )

doing stuff with object: 1
processing events
event type: 0
event print: 0 print implementation
doing stuff with object: 2
processing events
event type: 0
event print: 0 print implementation
doing stuff with object: 3
processing events
event type: 0
event print: 0 print implementation
doing stuff with object: 4
processing events
event type: 0
event print: 0 print implementation
doing stuff with object: 5
processing events
event type: 0
event print: 0 print implementation
end of while loop

Output after cloning:

doing stuff with object: 1
doing stuff with object: 2
doing stuff with object: 3
doing stuff with object: 4
doing stuff with object: 5
end of while loop

the m_event_queue does not get updated in the B clone object

1

There are 1 answers

6
Alan Birtles On

The reference to eventQueue in the cloned DerivedA is a reference to the original BaseB's eventQueue, when the original is destroyed by test = std::move(test_history.back()); the reference becomes invalid.

I'm not sure how you could fix this, it probably depends on your actual use case.