Учитывая следующую проблему:
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();
Результат
На самом деле, я столкнулся с несколькими проблемами с моим подходом. Я сделал много рефакторинга, используя этот пост: Как можно уменьшить std :: shared_ptr? . Спасибо всем за помощь 🙂
Вероятно, дизайн можно улучшить, чтобы устранить эту проблему, но работая в рамках существующего дизайна, вы можете добавить функцию виртуального члена. Instrument::play_it
это занимает Player
как полиморфный аргумент. В Player
иметь две функции play_guitar
(принимая гитарный аргумент) и play_piano
(принимая фортепианные аргументы). В классе игры на гитаре play_it
звонить Player::play_guitar
с собой в качестве аргумента. В классе фортепиано play_it
звонить Player::play_piano
с собой в качестве аргумента. Смотри, ма не бросает.
Это не совсем многократная рассылка, она известна как шаблон посетителя. Однако, пожалуй, лучше не зацикливаться на этом, чтобы не начать называть вещи visitor
или такая неописательная глупость.
Двойная рассылка работает следующим образом (псевдокод, важные, но тривиальные вещи опущены):
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);
};
Тип стирания это еще один вариант:
std::vector<std::function<void()>> playInstrument;
playInstrument.emplace_back([g = Guitar{}]() { return g.doGuitar(); });
playInstrument.emplace_back([p = Piano{} ]() { return p.doPiano(); });
playInstrument[0]();
Для этого вам даже не нужен общий базовый класс.
Один из способов определения классов во время выполнения — использовать 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();