Verthais
Verthais

Reputation: 447

How does inheritance works in case of factory methods in cpp?

I'm trying to solve this simple riddle at codingames and I thought i will exercise in OOP However, it seems I've forgotten how CPP works in this field and I got an error I do not comprehend.

/tmp/Answer.cpp:82:1: error: invalid abstract return type ‘Sign’
   82 | Sign from_str(const int value, const std::string& s)
      | ^~~~
/tmp/Answer.cpp:14:7: note:   because the following virtual functions are pure within ‘Sign’:
   14 | class Sign {
      |       ^~~~
/tmp/Answer.cpp:22:25: note:    ‘virtual std::string Sign::str() const’
   22 |     virtual std::string str() const = 0;
      |                         ^~~
/tmp/Answer.cpp:82:6: error: invalid abstract return type for function ‘Sign from_str(int, const string&)’
   82 | Sign from_str(const int value, const std::string& s)
      |      ^~~~~~~~
/tmp/Answer.cpp: In function ‘Sign from_str(int, const string&)’:
/tmp/Answer.cpp:85:26: error: cannot allocate an object of abstract type ‘Sign’
   85 |         return Rock(value);
      |                          ^
/tmp/Answer.cpp:87:27: error: cannot allocate an object of abstract type ‘Sign’
   87 |         return Paper(value);
      |                           ^
/tmp/Answer.cpp:89:30: error: cannot allocate an object of abstract type ‘Sign’
   89 |         return Scissors(value);
      |                              ^
/tmp/Answer.cpp:91:28: error: cannot allocate an object of abstract type ‘Sign’
   91 |         return Lizard(value);
      |                            ^
/tmp/Answer.cpp:93:27: error: cannot allocate an object of abstract type ‘Sign’
   93 |         return Spock(value);

And the code looks like this:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class Rock;
class Paper;
class Scissors;

class Sign {
public:
    Sign(const int v): value(v) {};
    virtual ~Sign() {};
    bool operator<(const Sign& other) { return value < other.value ? false : true; }
    virtual std::string str() const = 0;

    int value{};
};

class Rock : public Sign {
public:
    Rock(const int v): Sign(v) {};
    bool operator<(const Paper& other) { return true; }
    bool operator<(const Scissors& other) { return false; }
    std::string str() const override { return "Rock"; }

};

class Paper : public Sign {
public:
    Paper(const int v): Sign(v) {};
    bool operator<(const Rock& other) { return true; }
    bool operator<(const Scissors& other) { return false; }
    std::string str() const override { return "Paper"; }
};

class Scissors : public Sign {
public:
    Scissors(const int v): Sign(v) {};
    bool operator<(const Rock& other) { return false; }
    bool operator<(const Paper& other) { return true; }
    std::string str() const override { return "Scissors"; }
};


Sign from_str(const int value, const std::string& s)
{
    if(s == "R")
        return Rock(value);
    if(s == "P")
        return Paper(value);
    if(s == "C")
        return Scissors(value);
    
    throw 1;
}

int main()
{
    int N;
    cin >> N; cin.ignore();
    std::vector<Sign> s{};

    for (int i = 0; i < N; i++) {
        int NUMPLAYER;
        string SIGNPLAYER;
        cin >> NUMPLAYER >> SIGNPLAYER; cin.ignore();
        s.emplace_back(from_str(NUMPLAYER, SIGNPLAYER));
    }
}

At this point, I don't really understand why I can't use Sign as the return value from the factory method that is returning concrete types and emplace it on my data pile.

And if I add to the base class

virtual std::string str() const { return "Sign"; };

I will only get the base class printout.

Upvotes: 0

Views: 84

Answers (2)

Jarod42
Jarod42

Reputation: 217478

Without allocation, you might use std::variant:

struct Rock
{
    std::string str() const { return "Rock"; }
};

struct Paper
{
    std::string str() const { return "Paper"; }
};

struct Scissors
{
    std::string str() const { return "Scissors"; }
};

using Sign = std::variant<Rock, Paper, Scissors>;

bool winSigns (Paper, Rock) { return true; }
bool winSigns (Rock, Scissors) { return true; }
bool winSigns (Scissors, Paper) { return true; }
template <typename Sign1, typename Sign2>
bool winSigns (Sign1, Sign2) { return false; }

Sign from_str(const std::string& s)
{
    if (s == "R")
        return Rock{};
    if (s == "P")
        return Paper{};
    if (s == "C")
        return Scissors{};
    throw 1;
}

int main()
{
    std::string SIGNPLAYER1;
    std::cin >> SIGNPLAYER1;
    Sign sign1 = from_str(SIGNPLAYER1);
    std::string SIGNPLAYER2;
    std::cin >> SIGNPLAYER2;
    Sign sign2 = from_str(SIGNPLAYER2);

    std::visit([](auto lhs, auto rhs){
        std::cout << lhs.str() << "versus" << rhs.str() << std::endl;
        if (winSigns (lhs, rhs)) std::cout << "player1 wins\n";
        else if (winSigns (rhs, lhs)) std::cout << "player2 wins\n";
        else std::cout << "Draw\n";

    }, sign1, sign2);
}

Demo

Upvotes: 0

catnip
catnip

Reputation: 25388

You are sufferring from object slicing, both in your return type from from_str and in your vector <Sign>.

Like it or not, you are going to have to use pointers, but if you use smart pointers then the pain will go away. So, first change your from_str function like so:

std::unique_ptr <Sign> from_str(const int value, const std::string& s)
{
    if(s == "R")
        return std::make_unique <Rock> (value);
    if(s == "P")
        return std::make_unique <Paper> (value);
    if(s == "C")
        return std::make_unique <Scissors> (value);
    
    throw 1;
}

And then change your vector to:

std::vector<std::unique_ptr <Sign>> s{};

And that's all you need to change. The fact that you're using std::unique_ptr takes care of any memory management issues.

Upvotes: 2

Related Questions