0

I have a program where I have a vector of pointers to a base class where I store instances of multiple different derived classes. All these objects interact with each other in different ways depending on their type and I'd like to implement these interactions in a smart way. I'd prefer to have a mediator class where to keep as much as possible of the code related to these interactions.

Here's a simplified version of what I'd like to achieve:

#include <iostream>
#include <vector>

class Gesture {};

class Rock : public Gesture {};

class Paper : public Gesture {};

class Scissors : public Gesture {};

class Game {
    public:
        static void play(Rock* first, Rock* second) {
            std::cout << "Rock ties with rock." << std::endl;
        }
        static void play(Rock* first, Paper* second) {
            std::cout << "Rock is beaten by paper." << std::endl;
        }
        static void play(Rock* first, Scissors* second) {
            std::cout << "Rock beats scissors." << std::endl;
        }
        static void play(Paper* first, Rock* second) {
            std::cout << "Paper beats rock." << std::endl;
        }
        static void play(Paper* first, Paper* second) {
            std::cout << "Paper ties with paper." << std::endl;
        }
        static void play(Paper* first, Scissors* second) {
            std::cout << "Paper is beaten by scissors." << std::endl;
        }
        static void play(Scissors* first, Rock* second) {
            std::cout << "Scissors are beaten by rock." << std::endl;
        }
        static void play(Scissors* first, Paper* second) {
            std::cout << "Scissors beat paper." << std::endl;
        }
        static void play(Scissors* first, Scissors* second) {
            std::cout << "Scissors tie with scissors." << std::endl;
        }
};

int main()
{   
    Rock rock;
    Paper paper;
    Scissors scissors;
    
    std::vector<Gesture*> gestures;
    gestures.push_back(&rock);
    gestures.push_back(&paper);
    gestures.push_back(&scissors);
    
    for(int i = 0; i < 3; ++i) {
        for(int j = 0; j < 3; ++j) {
            // alas, downcasting doesn't happen automagically...
            Game::play(gestures[i], gestures[j]);
        }
    }

    return 0;
}

Is there an easy way to achieve downcasting without compile-time knowledge which derived classes are in question? The accepted answer to this question: Polymorphism and get object type in C++ seems to solve my problem, but I'm wondering if I can avoid changes to the derived classes (I'd like to collect the related code into the mediator class, if possible). I suppose I could figure out the derived class with a switch statement that tries to downcast to all the possible derived classes and check which one doesn't return a nullptr, but this doesn't seem particularly "smart".

Any ideas?

2 Answers2

0

My typical response to this situation is to add a field to the base class of type enum.

typedef enum {rockType,paperType,scissorsType} fred;

class Gesture
{
    virtual fred Type() = 0;
}

class Rock : public Gesture
{
    fred Type() { return typeRock;}
}

and so on. Then you can call each object's Type() function and use a switch statement to dispatch each object.

Logicrat
  • 4,438
  • 16
  • 22
  • 1
    did you mean `fred Type() { return typeRock;}` – user Nov 16 '20 at 21:56
  • Hmm, introducing `enum`s to the classes isn't too invasive - that's good. But I don't like the idea of nested `switch` statements. In reality I currently have five different derived classes (and no, the two others aren't "Lizard" and "Spock") which would result in a rather lengthy `switch` statement. So while this is an improvement on my plan C, I'd still go with plan B ([Polymorphism and get object type in C++](https://stackoverflow.com/questions/41320594/polymorphism-and-get-object-type-in-c)). – Petri Hirvonen Nov 17 '20 at 15:23
  • Nevermind, I either didn't read or think fully about what's described behind the link - it doesn't solve my problem fully. Edit: I mean I'll still need one `switch` statement or something. – Petri Hirvonen Nov 17 '20 at 18:06
0

Since I can't seem to avoid using an ugly switch statement or a chain of if-else if, I think I'll just do something like the following where I don't need to introduce enums to the classes or modify them otherwise.

#include <iostream>
#include <vector>

class Gesture {
    virtual void foo() {}
};

class Rock : public Gesture {
    virtual void foo() {}
};

class Paper : public Gesture {
    virtual void foo() {}
};

class Scissors : public Gesture {
    virtual void foo() {}
};

class Game
{
    public:
        static void rock_vs_rock() {
            std::cout << "Rock ties with rock." << std::endl;
        }
        static void rock_vs_paper() {
            std::cout << "Rock is beaten by paper." << std::endl;
        }
        static void rock_vs_scissors() {
            std::cout << "Rock beats scissors." << std::endl;
        }
        static void paper_vs_rock() {
            std::cout << "Paper beats rock." << std::endl;
        }
        static void paper_vs_paper() {
            std::cout << "Paper ties with paper." << std::endl;
        }
        static void paper_vs_scissors() {
            std::cout << "Paper is beaten by scissors." << std::endl;
        }
        static void scissors_vs_rock() {
            std::cout << "Scissors are beaten by rock." << std::endl;
        }
        static void scissors_vs_paper() {
            std::cout << "Scissors beat paper." << std::endl;
        }
        static void scissors_vs_scissors() {
            std::cout << "Scissors tie with scissors." << std::endl;
        }
        static void play(Gesture* first, Gesture* second)
        {
            // I've never really liked switch statements...
            if(dynamic_cast<Rock*>(first) && dynamic_cast<Rock*>(second))
            {
                rock_vs_rock();
            }
            else if(dynamic_cast<Rock*>(first) && dynamic_cast<Paper*>(second))
            {
                rock_vs_paper();
            }
            else if(dynamic_cast<Rock*>(first) && dynamic_cast<Scissors*>(second))
            {
                rock_vs_scissors();
            }
            else if(dynamic_cast<Paper*>(first) && dynamic_cast<Rock*>(second))
            {
                rock_vs_rock();
            }
            else if(dynamic_cast<Paper*>(first) && dynamic_cast<Paper*>(second))
            {
                paper_vs_paper();
            }
            else if(dynamic_cast<Paper*>(first) && dynamic_cast<Scissors*>(second))
            {
                paper_vs_scissors();
            }
            else if(dynamic_cast<Scissors*>(first) && dynamic_cast<Rock*>(second))
            {
                scissors_vs_rock();
            }
            else if(dynamic_cast<Scissors*>(first) && dynamic_cast<Paper*>(second))
            {
                scissors_vs_paper();
            }
            else if(dynamic_cast<Scissors*>(first) && dynamic_cast<Scissors*>(second))
            {
                scissors_vs_scissors();
            }
        }
};

int main()
{   
    Rock rock;
    Paper paper;
    Scissors scissors;
    
    std::vector<Gesture*> gestures;
    gestures.push_back(&rock);
    gestures.push_back(&paper);
    gestures.push_back(&scissors);
    
    for(int i = 0; i < gestures.size(); ++i)
    {
        for(int j = 0; j < gestures.size(); ++j)
        {
            Game::play(gestures[i], gestures[j]);
        }
    }

    return 0;
}
  • Of course if this was a performance critical part, then checking `enum`s would surely be much faster than `dynamic_cast`s. One could also have `std::vector> gestures` as an alternative to `enum` members. – Petri Hirvonen Nov 18 '20 at 08:07