Polymorphism and get object type in C++

1.5k views Asked by At

Let's say that I have 4 classes, B,C,D,E that inherit from A(abstract base class). Also I have a container (std::vector) of type A* whose contents point to either of B,C,D,E objects. Here are some rules: If a B object and a C object interact, they get removed from the vector and in their place a D object gets created.

Also, C + D = E

Now, suppose that I randomly choose one of said vector contents; how would I go about knowing which object is of which type, in order to implement an interaction mechanic?

NOTE: I do not wish to use the typeid operator, a dynamic cast or flags. Any other solutions?

Here is some code

#include <iostream>

class A {
protected:
    int v;
public:
    A(){}
    ~A(){}
};

class B :public A {
public:
    B(){}
    ~B(){}
};

class C : public A {
public:
    C(){}
    ~C(){}
};

class D : public A {
public:
    D(){}
    ~D(){}
};

class E : public A {
public:
    E(){}
    ~E(){}
};

int main()
{
    std::vector<A*> container;
    return 0;
}

How would I implement the interact function(s) ?

2

There are 2 answers

0
Jarod42 On BEST ANSWER

You may use virtual functions to do the multiple dispatch

struct B;
struct C;
struct D;
struct E;

struct A
{
    virtual ~A() = default;

    virtual std::unique_ptr<A> interactWithA(const A&) const = 0;

//protected:
    virtual std::unique_ptr<A> interactWithB(const B&) const = 0;
    virtual std::unique_ptr<A> interactWithC(const C&) const = 0;
    virtual std::unique_ptr<A> interactWithD(const D&) const = 0;
    virtual std::unique_ptr<A> interactWithE(const E&) const = 0;
};

// Your interact rules

template <typename LHS, typename RHS>
std::unique_ptr<A> interact(const LHS&, const RHS&) { return nullptr; }

// Note that definitions and declarations must be split in reality
// to be able to compile it
std::unique_ptr<A> interact(const B&, const C&) { return std::make_unique<D>(); }
std::unique_ptr<A> interact(const C&, const D&) { return std::make_unique<E>(); }
// Maybe the reflexive case, C/B D/C ?


// The derived classes
struct B : A
{
    std::unique_ptr<A> interactWithA(const A& a) const override { return a.interactWithB(*this); }

    // Even if code look similar for other inherited class
    // the difference is in the runtime type of the objects are known.
    std::unique_ptr<A> interactWithB(const B& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithC(const C& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithD(const D& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithE(const E& rhs) const override { return interact(rhs, *this); }
};

struct C : A
{
    std::unique_ptr<A> interactWithA(const A& a) const override { return a.interactWithC(*this); }
    std::unique_ptr<A> interactWithB(const B& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithC(const C& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithD(const D& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithE(const E& rhs) const override { return interact(rhs, *this); }
};

struct D : A
{
    std::unique_ptr<A> interactWithA(const A& a) const override { return a.interactWithD(*this); }
    std::unique_ptr<A> interactWithB(const B& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithC(const C& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithD(const D& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithE(const E& rhs) const override { return interact(rhs, *this); }
};

struct E : A
{
    std::unique_ptr<A> interactWithA(const A& a) const override { return a.interactWithE(*this); }
    std::unique_ptr<A> interactWithB(const B& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithC(const C& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithD(const D& rhs) const override { return interact(rhs, *this); }
    std::unique_ptr<A> interactWithE(const E& rhs) const override { return interact(rhs, *this); }
};

and then

std::vector<std::unique_ptr<A>> v /* =  .. */;

auto a = v[i]->interactWithA(*v[j]);
if (a) {
    // Remove v[i] and v[j]
    // Insert a
}
2
Incomputable On

Your problem sounds like a bad abstraction. So actually you're not solving the right problem. You should use inheritance when you don't need to know the exact type of the object, but instead rely on runtime polymorphism.

You can bake in some flags, like virtual function that will return an id of each type, but is rather workaround, not a solution. It is also easy to get it wrong.

class A
{
    ...
    virtual int get_id() = 0;
}

Variants

Instead of polymorphism, if the types are fixed (e.g. you don't plan to add or remove classes), you can use std::variant<> (C++17) or boost.variant. To interact with it, you will need to use visitors and call std::visit(). May be it will be harder to interact with it, but, in my opinion, it will better fit as a solution for the problem you described.