【问题标题】:C++ Multiple DispatchC++ 多分派
【发布时间】:2016-12-12 09:23:09
【问题描述】:

鉴于以下问题:

class Instrument {
};

class Guitar : public Instrument {
  public:
    void doGuitar() const;
};

class Piano : public Instrument {
  public:
    void doPiano() const;
};

我得到了指向Instrument的指针列表

list<shared_ptr<Instrument>> instruments;

我在其中添加乐器(例如)

Guitar myGuitar;
instruments.push_back(make_shared<Guitar>(myGuitar));

现在,我想遍历列表instruments 并调用doPiano(),如果当前乐器是钢琴,doGuitar() 如果它是吉他。这两个函数差别很大,因此不能在 Instrument 类中抽象化。

问题是 C++ 无法通过运行时识别 Instrument 的类型,不是吗(由于单次调度)?根据迭代器指向的当前类型,如何实现它调用钢琴或吉他功能。

如果我能实现某事,我会很高兴。像这样的伪代码工作:

list<shared_ptr<Instrument>>::const_iterator it;
if ("current type == Guitar")
  (*it)->doGuitar();
else if ("current type == Piano")
  (*it)->doPiano();

结果

实际上,我的方法遇到了几个问题。我使用这篇文章做了很多重构:How does one downcast a std::shared_ptr?。感谢大家的帮助:)

【问题讨论】:

  • 为什么不简单地使用一个虚拟的do 函数来做正确的事情?
  • 看看std::dynamic_pointer_cast。然后撕掉你的设计并重新开始。多态性只适用于所有派生类可以合理共享同一个接口的情况。
  • 其实,我的例子只是我“真实”实现的一小部分。 Instrument 中有几个纯虚函数。我应该提到它。
  • 既然你提到了多次调度,你可能想看看visitor pattern
  • "这两个函数差别很大,因此不能在 Instrument 类中抽象化。"显示真实代码。就目前而言,do_piano 和 do_guitar 完全一样,应该在 Instrument 中作为一个通用的纯虚函数。你需要一个令人信服的相反论据。

标签: c++ inheritance polymorphism multiple-dispatch


【解决方案1】:

设计可能会被改进以消除这个问题,但是在现有设计中,您可以添加一个虚拟成员函数Instrument::play_it,它将Player 作为多态参数。在Player 中有两个函数play_guitar(接受吉他参数)和play_piano(接受钢琴参数)。在吉他类中覆盖 play_it 以调用 Player::play_guitar 并以 self 作为参数。在钢琴类中覆盖 play_it 以调用 Player::play_piano 并以 self 作为参数。看起来没有演员表。

这不完全是多次调度,它被称为访问者模式。不过,最好不要过多关注这一点,以免您开始将事物命名为 visitor 或此类非描述性的愚蠢行为。

【讨论】:

    【解决方案2】:

    双重调度的工作方式是这样的(伪代码,省略了重要但琐碎的东西):

    struct InstrumentVisitor{
       // knows all instruments
       virtual void doGuitar(Guitar*) = 0;
       virtual void doPiano(Piano*) = 0;
    };
    
    class Instrument {
       virtual void doInstrument(InstrumentVisitor*) = 0;
       ...
     };
    
    class Piano : public Instrument {
        void doInstrument (InstrumentVisitor* v) {
           v->doPiano(this);
    };
    
    class Guitar : public Instrument {
        void doInstrument (InstrumentVisitor* v) {
           v->doGuitar(this);
    };
    

    现在我们可以设计具体的访问者了。

    struct Player : InstrumentVisitor {
      // does vastly different things for guitar and piano
       void doGuitar (Guitar* g) {
          g->Strum(pick, A6);
       }
       void doPiano (Piano* p) {
          p->Scale (Am, ascending);
    };
    

    【讨论】:

    • @Cheersandhth.-Alf 因为不必写*this,这有时会让人感到困惑。但这是一个品味问题。使用引用或指针,我不会反对。
    【解决方案3】:

    类型擦除是另一种选择:

    std::vector<std::function<void()>> playInstrument;
    playInstrument.emplace_back([g = Guitar{}]() { return g.doGuitar(); });
    playInstrument.emplace_back([p = Piano{} ]() { return p.doPiano();  });
    
    playInstrument[0]();
    

    为此,您甚至不需要通用基类。

    【讨论】:

      【解决方案4】:

      在运行时识别类的一种方法是使用dynamic_cast。但是要使用它,你需要在你的类中至少有一个虚拟方法。为此,可以将一个空的虚拟方法添加到仪器类中。

      class Instrument {
        private:
          virtual void emptyMethod_doNotCall() {} // Add this method.
      };
      
      class Guitar : public Instrument {
        public:
          void doGuitar() const;
      };
      
      class Piano : public Instrument {
        public:
          void doPiano() const;
      };
      

      可以通过对目标类指针执行dynamic_cast 来检查对象类型。如果无法将对象强制转换为所需的目标类,dynamic_cast 将返回 NULL

      list<shared_ptr<Instrument>>::const_iterator it;
      if (dynamic_cast<Guitar*>(it) != NULL)
        (*it)->doGuitar();
      else if (dynamic_cast<Piano*>(it) != NULL)
        (*it)->doPiano();
      

      【讨论】:

      • 每次添加一个新的仪器,你都需要检查所有的代码,手动添加这个新仪器的情况......那为什么还要麻烦一个类层次结构呢?
      • 为什么不直接创建析构函数virtual?毕竟,既然你是通过基指针存储的,它就必须是。
      • @UmNyobe:OP 说“这两个函数有很大不同,因此不能在类 Instrument 中抽象化”。这就是我建议进行动态演员表的原因。
      • 当您需要在今天 09:00 之前保存失败的设计时,以这样的模式动态投射是您的最后手段,现在是 7:55,并且您在上周有 3 小时的睡眠.
      • 尝试此方法,导致以下编译器错误:cannot dynamic_cast 'it' (of type 'struct _list_const_iterator&lt;shared_ptr&lt;Instrument&gt;&gt;'') to type 'class Guitar*' (source is not a pointer).
      猜你喜欢
      • 2010-12-17
      • 2019-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多