Search code examples
c++ooppointersinheritanceabstract-class

Reference to child class lost after assigning to a base instance


I'm trying to implement a Runner class that handles different types of objects dynamically, this Runner should be agnostic of what type of object is handling and use abstract class methods to execute functions which the child classes will be in charge of executing their own implementation.

All properties in the Runner class are treated as generic objects, but whenever a child implementation is executed, that implementation is not visible to the Runner because assigning child objects into base type makes the child reference to be lost.

What would be the best implementation for this architecture?

This is my code:

#include <vector>
#include <iostream>

class GenericItem{
    public:
        virtual void f() {};
};

class GenericList {
    public:
        virtual void f() {};
        std::vector<GenericItem*> list;
};

class Apple: public GenericItem{
    public:
        Apple(int color){
            this->color = color;
        }
        int color;
};

class House: public GenericItem{
    public:
        int size;
};

class AppleList: public GenericList{
    public:
        std::vector<Apple*> list;
};

class HouseList: public GenericList{
    public:
        std::vector<House*> list;
};

class GenericManager{
    public:
        virtual GenericList* getList() = 0;
};

class AppleManager: public GenericManager{
    public:
        AppleManager(){}
        AppleList* getList() {
            AppleList* list = new AppleList();
            list->list.push_back(new Apple(5));
            list->list.push_back(new Apple(7));
            list->list.push_back(new Apple(9));
            return list;
        }
};

class Runner{
    public:
        Runner(GenericManager* manager){
            this->manager = manager;
        }
        GenericItem* chooseItem(){
            GenericList* list = this->manager->getList();
            std::cout << "Vector size: " << list->list.size() << std::endl;
            return list->list.front();
        }
        GenericManager* manager;
};

int main (){
    Runner runner(new AppleManager());
    Apple* apple = dynamic_cast<Apple*>(runner.chooseItem());
    std::cout << "Apple color: " << apple->color << std::endl;
};

This is the output I got:

Vector size: 0
Segmentation fault (core dumped)

Expected output:

Vector size: 3
Apple color: 5

In this case the function getList() always returns an object with a vector of 3 elements, but when is called by the Runner the object has an empty list.


Solution

  • The problem comes from the generic list.

    Here you declare a vector of generic items:

    // Inside GenericList
    std::vector<GenericItem*> list;
    

    But then, you also declare another vector in its child class:

    // inside AppleList
    std::vector<Apple*> list;
    

    This will shadow the parent's list.

    You can't override members like functions, as they have distinct instances and distinct types. Also note that std::vector<Apple*> is completely unrelated to std::vector<GenericItem*>, they don't have any relationship, and cannot be converted from one to another.

    You can verify that you have indeed two distinct vector by doing this:

    AppleList* getList() {
        AppleList* list = new AppleList();
        list->list.push_back(new Apple(5));
        list->list.push_back(new Apple(7));
        list->list.push_back(new Apple(9));
    
        std::cout << list->list.size() << list->GenericList::list.size();
    
        return list;
    }
    

    You will see 30 in the output.

    An easy solution would be to simply remove the child's vector, and only use the parent's one:

    class GenericList {
        public:
            virtual void f() {};
            std::vector<GenericItem*> list;
    };
    
    class AppleList: public GenericList{
    };
    
    class HouseList: public GenericList{
    };
    

    Fundamentally, you cannot reinterpret an array of a type to be an array of a different type. This is not a std::vector limitation, but a limitation of (any) OO langages that allow multiple inheritance since the Apple part of each GenericItem might be at different places and each of the items need to be casted one by one.