Использование чисто полиморфизма и наследования для вызова функций из производного класса из базового класса без приведения? - PullRequest
2 голосов
/ 04 марта 2020

Моя проблема: у меня есть карточная игра, она состоит из миньонов, заклинаний и снаряжения, которые являются всеми типами карт, я знаю виртуальные определения методов. Мне нужно использовать функции GetHealth(), RemoveHealth() и AddHealth() на Minion, но я не хочу использовать их на Spell или Equipment. Что я должен сделать sh, это:

class CCard
{
//Member variables and functions
};

class CMinion : public CCard
{
    //Member variables and functions
    int GetHealth() const {return mHealth;}
};

std::unique_ptr<CCard> minion = std::make_unique<CMinion>(params);
minion->GetHealth(); //I realize this isn’t possible the way it is.

В настоящее время у меня есть отдельный список миньонов с и идентификатором, связанным с картой, когда карта находится в моей руке, я вместо этого помещаю объект Minion на столе, а не на карте. Мой лектор хочет чистого полиморфизма и наследования , так что это неуклюжее решение. Я также обсуждал помещение GetHealth() и других необходимых методов в классе Card и возвращение к классам, которые не нуждаются в нем, 0. Он сказал: «Это лучшее решение, но опять же я должен использовать наследование и полиморфизм».

Он считает, что моя архитектура неверна, и я должен ее реструктурировать.

У меня должно быть: Вектор карт, являющийся экземпляром Minion, Spell или Equipment. Стол, рука и колода должны быть векторами карт, но мое текущее решение - колода и рука - векторы карт, а моя таблица - вектор миньонов.

Может кто-нибудь придумать лучшую структуру или эффективное решение?

Ответы [ 3 ]

1 голос
/ 04 марта 2020

Как упоминал Jarod42 в своем комментарии к исходному вопросу, вы можете использовать шаблон посетителя .

. Это позволяет вам получить фактический тип объекта Card без какого-либо слепки. Это также известно как двойная отправка.

Это позволяет вам помещать функции, указывающие c на карту определенного типа, только в тип этой карты. Т.е. нет необходимости помещать виртуальную GetHealth () функцию по умолчанию в Card, которая выдает исключение. Просто объявите функцию GetHealth() в типе карты Minions, Spell не будет иметь GetHealth().

Вот небольшой пример:

// Forward declare visitors so Accept function can be delared in Card.
struct CardVisitor;
struct ConstCardVisitor;

struct Card {
  virtual ~Card () = default;

  virtual void Activate () = 0;

  virtual void Accept (CardVisitor & obj) = 0;
  virtual void Accept (ConstCardVisitor & obj) const = 0;
};

// Forward declare derived Card types, so Visit function can be declared in visitors.
struct Minion;
struct Spell;

struct CardVisitor {
  virtual void Visit (Minion & obj) = 0;
  virtual void Visit (Spell & obj) = 0;
};

struct CardVisitor {
  virtual void Visit (Minion const & obj) = 0;
  virtual void Visit (Spell const & obj) = 0;
};

struct Minion : Card {
  int GetHealth () const;

  virtual void Activate () override;

  // Copy-paste exactly this code in every "final" type deriving from Card.
  virtual void Accept (CardVisitor & obj) override { obj.Visit(*this); }
  virtual void Accept (ConstCardVisitor & obj) const override { obj.Visit(*this); }
}

struct Spell : Card {
  int GetSmokeColor () const;
  bool DoesInstaKill () const;

  virtual void Activate () override;

  // Copy-paste exactly this code in every "final" type deriving from Card.
  virtual void Accept (CardVisitor & obj) override { obj.Visit(*this); }
  virtual void Accept (ConstCardVisitor & obj) const override { obj.Visit(*this); }
}

Затем создайте посетителя это делает все, что требуется. Например, давайте напечатаем некоторую информацию о картах:

#include <iostream>
#include <memory>

struct PrintVisitor : ConstCardVisitor {
  virtual void Visit (Minion const & obj) override {
    std::cout << "health: " << obj.GetHealth();
  }

  virtual void Visit (Spell const & obj) override {
    std::cout << "smoke color: " << obj.GetSmokeColor()
      << ", does insta kill: " << obj.DoesInstaKill();
  }
}

int main () {
  // Base class pointers, i.e. to Card.
  std::unique_ptr<Card> cardA = std::make_unique<Minion>();
  std::unique_ptr<Card> cardB = std::make_unique<Spell>();

  PrintVisitor printVisitor;
  cardA->accept(printVisitor);
  cardB->accept(printVisitor);
};

PS: деструктор Card должен быть виртуальным. Это не в вашем примере кода. В этом случае ваши производные объекты не будут корректно удалены при вызове delete для указателя Card (или когда std::unique_ptr сделает это за вас).

Редактировать : Добавлено Spell класс, чтобы сделать использование шаблона посетителя более понятным.

0 голосов
/ 04 марта 2020

Вам нужно либо поставить GetHealth в Card, либо использовать приведение позже. Другого пути нет. Поскольку раздача исключена, это означает, что Card необходимо GetHealth.

. Чтобы избежать разыгрывания, вы можете сохранить тип карты в качестве элемента данных в Card и использовать не виртуальный * 1008. * функция. Конструкторы различных классов типов карт будут затем конструировать свою Card часть, используя соответствующий тип.

Для предотвращения автономного построения базовых классов карт, таких как Card, Minion et c и позволяют создавать только реальные карты (например, Goblin, Armor, et c), защищают конструкторы, определяющие тип:

class Card
{
public:
    enum class Type { Minion, Spell, Equipment };

    virtual ~Card() = default;
    Type type() const { return type_; }
    virtual int GetHealth() const { return 0; }

protected:
    explicit Card(const Type type) { type_ = type; }

private:
    Type type_;
};

class Minion : public Card
{
public:
    int GetHealth() const override { return mHealth; }

protected:
    Minion() : Card(Type::Minion) { }

private:
    int mHealth = 100;
};

class Goblin : public Minion
{ };

class Equipment : public Card
{
protected:
    Equipment() : Card(Type::Equipment) { }
};

class Armor : public Equipment
{ };

void test(Card& card)
{
    if (card.type() == Card::Type::Minion) {
        std::cout << "Health: " << card.GetHealth() << '\n';
    } else {
        std::cout << "Not a minion.\n";
    }
}

int main()
{
    Goblin goblin;
    Armor armor;
    test(goblin);
    test(armor);
}
0 голосов
/ 04 марта 2020

Я бы сделал это следующим образом:

class Card
{
public:
    virtual ~Card() = default;

    template <typename T>
    T* As() { return dynamic_cast<T*>(this); }
};

class Minion : public Card
{
public:
    int GetHealth() const {return mHealth;}

private:
    int mHealth = 100;
};

void test()
{
    std::unique_ptr<Card> card = std::make_unique<Minion>();
    if (auto* minion = card->As<Minion>())
        minion->GetHealth();
}

Часть if (auto* minion = ... - это компактный способ проверить, является ли тип Minion, а также получить указатель на него, чтобы вы могли вызывать методы которые не существуют в базовом классе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...