如何在没有类型代码的情况下普遍引用类的实例?

时间:2017-06-02 22:35:08

标签: c++ oop c++11 polymorphism

我正在创建一个基于文本的RPG,其中我有一个抽象的Item类。在这个Item课程中,我有课程WeaponPotionKeyArmor。主要角色类Protagonist使用这些项目并具有函数doItemEffect(Item*)。如何以一种普遍引用所有项目的方式实现doItemEffect(Item*)?为了更好地表达我的问题,如果我不清楚,这是一个使用非常难看的解决方案的例子。

class Protagonist
{
public:
    void doItemEffect(Item* it)
    {
        switch(it->getType()) //<-- The type is an enum class
        {
        case ItemType::WEAPON:
            attackOpponent(it.getAttackPower()); //If it is a weapon it would have this function
            break;
        case ItemType::POTION:
            heal(it.getHealPower()); //If it is a weapon it would have this function
            break;
        case ItemType::KEY:
            //..Code..
            break;
        case ItemType::ARMOR:
            //More Code...
            break;
     }
};

两个类PotionWeapon的示例(类的类型是存储在Item中的私有变量,带有mutator方法setType()):

class Potion : public Item
{
    int healPower;
public:
    Potion(std::string name, std::string description) : Item(name, description)
    {
        setType(ItemType::POTION);
    }
    //Code
};

武器:

class Weapon : public Item
{
    int attackPower;
public:
    Weapon(std::string name, std::string description) : Item(name, description)
    {
        setType(ItemType::WEAPON);
    }
    //Code
};

如您所见,此代码依赖于Protagonist类中的类代码和开关。因此,这似乎不是面向对象或多态的。因此,有没有一种方法可以得到一个Item类型的子类,而不必使用类代码?或者还有其他解决方案吗?上面这个片段的另一个问题是每当我引用其类之外的项时,我必须对每种类型的项使用相同的switch语句。

3 个答案:

答案 0 :(得分:6)

use()课程中创建虚拟函数Item。从派生类中重写此函数以触发各种操作(攻击,修复等),以便所有子类项都有一个抽象接口来使用/应用它们。

答案 1 :(得分:0)

您可以使用RTTI(例如dynamic_cast<>())替代专用类型字段:

class Protagonist
{
public:
    void doItemEffect(Item* it)
    {
        Potion *potion = dynamic_cast<Potion *>(item);
        Weapon *weapon = dynamic_cast<Weapon *>(item);
        if (potion != nullptr) {
            heal(potion->getHealPower());
        }
        else if (weapon != nullptr) {
            attackOpponent(weapon->getAttackPower());
        }

或通过在抽象effect()类中添加虚拟Item类成员函数来使用多态:

class Item {
// ...
public:
    virtual void effect(Protagonist *) = 0;
// ...
};

并在派生类中重写它:

class Potion : public Item
{
// ...
public:
    void effect(Protagonist *) override;
};

,其缺点是您的Potion类需要知道它可以被Protagonist使用。为了解决这个问题,经常使用double dispatch。问题是C ++不支持双重调度作为语言功能。它可以使用访客模式进行模拟:

class Weapon;
class Potion;

class DispatchReceiver {
public:
    virtual void effect(Weapon *) = 0;
    virtual void effect(Potion *) = 0;
};

class Item {
// ...
public:
    virtual void effect(DispatchReceiver *) = 0;
// ...
};

class Potion : public Item {
// ...
    virtual void effect(DispatchReceiver *dr) override
    {
        dr->effect(this);
    }
// ...
};

class Weapon : public Item {
// ...
public:
    virtual void effect(DispatchReceiver *dr) override
    {
        dr->effect(this);
    }
// ...
};

class Protagonist : public DispatchReceiver {
// ...
public:
    void effect(Weapon *weapon) override
    {
        attackOpponent(weapon->getAttackPower());
    }
    void effect(Potion *potion) override
    {
        heal(potion->getHealPower());
    }
    void doItemEffect(Item* it)
    {
        it->effect(this);
    }
};

答案 2 :(得分:0)

有一个项目类型列表

template<class...Types>
struct type_list_t{};

using Items=type_list_t<Potion, Weapon, etc>;

这取代了你的枚举。您可以编写get索引类型,并从(编译时)索引中获取类型。您甚至可以编写我称之为魔术开关的内容,通过延续传递样式将运行时(有界)索引映射到编译时类型。

接下来向Item添加一个访问方法。它接受类型列表的索引,然后静态将其转换为子类型,然后调用带有转换结果的传入回调。

写函数重载,如下所示:

void use_item( Protagonist*, Potion* );
void use_item( Protagonist*, Weapon* );

然后使用Protagonist中的访问发送给它。

现在,您可以使用预先编写的variant来简化此操作。

template<class Base, class...Ts>
struct poly_variant:boost::variant<Ts...>{
  using boost::variant<Ts...>::variant;
  Base& base();
  Base const& base() const;
};

现在您可以访问Protagonist。如果要使存储成为指针(或智能指针),可以使用此变体。

通过申请访问者撰写base()