2

鉴于以下问题:

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()当当当当它是吉他时。这两个函数有很大不同,因此不能在 class 中抽象化Instrument

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

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

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

结果

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

4

4 回答 4

3

可能可以改进设计以消除此问题,但在现有设计中工作,您可以添加一个虚拟成员函数Instrument::play_it,该函数将 aPlayer作为多态参数。在Player有两个功能play_guitar(拿吉他参数)和play_piano(拿钢琴参数)。在吉他类覆盖play_itPlayer::play_guitarself 作为参数调用。在钢琴类覆盖play_itPlayer::play_pianoself 作为参数调用。看马没有演员表。

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

于 2016-12-12T09:37:39.950 回答
0

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

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);
};
于 2016-12-12T09:57:54.727 回答
0

类型擦除是另一种选择:

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

playInstrument[0]();

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

于 2016-12-12T11:44:56.353 回答
-2

在运行时识别类的一种方法是使用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可以通过对目标类指针执行 a 来检查对象类型。如果对象无法转换为所需的目标类,则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();
于 2016-12-12T09:31:27.717 回答