c++ 在数据结构中存储子类型而不丢失对子类型方法的访问

ca1c2owp  于 2023-05-20  发布在  其他
关注(0)|答案(1)|浏览(104)

我正在编写一个模拟,其中各种代理相互作用和相互影响,代码目前的结构是这样的,所有具体的类继承自一个或多个类,定义什么行为可用于每个代理示例(如“交易者”或“谈话者”类),这些行为类也几乎继承自一个代理基类,定义行为和数据共同的所有代理。这是最终系统的简单模型

class agent {
    protected:
        int health;
        int mood;
        string name;
        vector<string> memories;
    public:
        agent(string name) {
            this->name = name;
            health = 20;
            mood = 0;
        }

        string getName() {
            return name;
        }

        void makeMemory(string memory) {
            memories.push_back(memory);
        }

        void takeDamage(int damage) {
            health -= damage;
        }

        void affectMood(int effect) {
            mood += effect;
        }
        
};

class fighter : public virtual agent {
    protected:
        int damage;
    public:
        virtual void attack(fighter* other) = 0;
};

class talker : public virtual agent {
    public:
        virtual void talk(talker* other) = 0;
};

struct item {
    string name;
    int value;
};

class trader : public virtual agent {  
    protected: 
        int money;
        map<string, item> inventory;
    public:
        bool spendMoney(int spend) {
            if(money - spend > 0) {
                money -= spend;
                return true;
            } else {
                return false;
            }
        }

        void reciveMoney(int income) {
            money += income;
        }

        void reciveItem(item item) {
            inventory[item.name] = item;
        }

        void dropItem(string itemName) {
            inventory.erase(itemName);
        }

        const map<string, item>& viewInventory() {
            return inventory;
        }

        virtual void trade(trader* other) = 0;
};

class FightAgent: public fighter {
    public:
        FightAgent(int damage, string name) : agent(name) {
            this->damage = damage;
        }

        void attack(fighter* other) {
            cout << name + " is fighting " + other->getName() << endl;
            other->takeDamage(damage);
        }
};

class TalkAgent: public talker {
    public:
        TalkAgent(string name) : agent(name) {};

        void talk(talker* other) {
            string memory =  this->name + " talked with " + other->getName();
            cout << memory << endl;
            this->makeMemory(memory);
            other->makeMemory(memory);
        }

};

class TalkTradeAgent: public talker, public trader {
    public:
        TalkTradeAgent(string name) : agent(name) {
            money = 100;
        }

        void trade(trader* other) {
            cout << name + " is trading with " + other->getName();
            for(pair<string, item> x : other->viewInventory()) {
                if(this->spendMoney(x.second.value)) {
                    other->reciveMoney(x.second.value);
                    other->dropItem(x.second.name);
                    this->reciveItem(x.second);
                    cout << " trade succesful";
                    break;
                }
            }
            cout << endl;
        }

        void talk(talker* other) {
            string memory =  this->name + " talked with " + other->getName();
            cout << memory << endl;
            this->makeMemory(memory);
            other->makeMemory(memory);
        } 
};

我正在努力构建一个数据结构,它可以让我以一种允许我访问它们实现的所有行为的方式存储所有最终代理示例
我最初的解决方案是使用一个vector<agent*>,并使用一个定义了它们实现的行为的结构体来组成基本代理类,并且在两个代理之间的交互中使用dynamic_cast向下转换到相关的直接代理子类。然而,这意味着每次添加新的代理行为子类时,我都需要更新行为结构体,并且需要动态转换,这很慢。我还试图实现一个结构,它可以接受代理子类的任何示例,并将其分解为指向其行为父类的指针,将它们存储在每个行为类的向量中,但是我在实现模板时遇到了困难。

i2byvkas

i2byvkas1#

这是个经验法则。如果你发现自己在写作

void do_thing(Base* x)
{
   if (auto a = dynamic_cast<A*>(x)) a->do_a_thing();
   else if (auto b = dynamic_cast<B*>(x)) b->do_b_thing();
   ...
}

那么do_thing应该是Base中的虚函数。
现在,当你有 * 两个 * Base派生的对象,并且你需要采取的操作依赖于这两种类型时,这就不那么好用了(或者根本不管用)。这个问题其实没有很好的解决办法。
一个有效的解决方案是 double dispatch,这是更通用的 *visitor模式 * 的一个特例。在双重分派中,每个类同时充当访问者和被访问者,而在访问者模式中,这些角色是分开的。我们在这里不讨论更一般的变体。
缺点是在基类和所有子类之间引入了循环依赖。这是一个人如何做到这一点:

class DerivedA;
class DerivedB;

class Base
{
   public:     virtual void interact(Base*) = 0;
   // Here the cyclic dependency comes
   protected:  virtual void do_interact(DerivedA*) {} 
               virtual void do_interact(DerivedB*) {} 
};

class DerivedA: public virtual Base
{
   void interact(Base* base) override { base->interact(this); }
   void do_interact(DerivedA* other) override
      { /* A-A-specific interaction */ }
   // A-B interaction is a no-op
};

class DerivedB: public virtual Base
{
   void interact(Base* base) override { base->interact(this); }
   void do_interact(DerivedB* other) override
      { /* B-B-specific interaction */ }
   // A-B interaction is a no-op
};

如果你有多重继承,并且每个基类独立交互,你只需要稍微改变一下:

class DerivedAB : public DerivedA, public DerivedB
{
    void interact(Base* base) override 
      { 
         DerivedA::interact(base);
         DerivedB::interact(base);
      }
};

有一个“非循环访问者”的变化。它以使用dynamic_cast为代价消除了循环依赖,但它以一种更可控的方式实现了这一点,在添加新子类时不一定需要重新访问所有内容。不要担心它会变慢,直到你测量你的性能并发现它是一个瓶颈(发生这种情况的可能性几乎为零)。你可以找到更多关于它的信息here

相关问题