Предположим, что у меня есть набор фруктов:
class Fruit { ... };
class Apple : public Fruit { ... };
class Orange: public Fruit { ... };
И некоторые полиморфные функции, которые воздействуют на упомянутые фрукты:
void Eat(Fruit* f, Pesticide* p) { ... }
void Eat(Apple* f, Pesticide* p) { ingest(f,p); }
void Eat(Orange* f, Pesticide* p) { peel(f,p); ingest(f,p); }
Хорошо, ожидать. Остановитесь тут же. Обратите внимание в этой точке, что любой нормальный человек сделал бы, Едят () виртуальную функцию членства Фруктовых классов. Но это не опция, потому что я не нормальный человек. Кроме того, я не хочу того Пестицида* в заголовочном файле для моего фруктового класса.
К сожалению, то, что я хочу смочь сделать затем, точно, что позволяют функции членства и динамическое связывание:
typedef list<Fruit*> Fruits;
Fruits fs;
...
for(Fruits::iterator i=fs.begin(), e=fs.end(); i!=e; ++i)
Eat(*i);
И очевидно, проблема здесь состоит в том, что указателем, который мы передаем для Еды () будет Фрукт*, не Apple* или Апельсин*, поэтому ничто не съедят, и мы все будем очень голодны.
Таким образом, что я действительно хочу смочь сделать вместо этого:
Eat(*i);
это:
Eat(MAGIC_CAST_TO_MOST_DERIVED_CLASS(*i));
Но к моим ограниченным знаниям, такое волшебство не существует, кроме возможно в форме большого противного оператора "if", полного вызовов к dynamic_cast.
Таким образом, действительно ли там некоторое время выполнения является волшебством, о котором я не знаю? Или я должен реализовать и поддержать большой противный оператор "if", полный dynamic_casts? Или я должен высосать его, выйти из взглядов о том, как я реализовал бы это в Ruby и позволил бы небольшому Пестициду превращать свой путь в мой фруктовый заголовок?
Обновление: Вместо изобретенного бита с пустым Едят функции и Пестицид, предполагают вместо этого, что я просто не хочу помещать, Едят во фруктах, потому что это не имеет никакого смысла. Фрукт, который знает, как съесть себя? Фыркать. Вместо этого мне нужен класс Едока со Съесть функцией с другим кодом для еды каждого вида фруктов и некоторого кода по умолчанию в случае, если это - фрукт, который не распознает едок:
class Eater
{
public:
void Eat(Apple* f) { wash(); nom(); }
void Eat(Orange* f) { peel(); nom(); }
void Eat(Fruit* f) { nibble(); }
};
...
Eater me;
for(Fruits::iterator i=fs.begin(), e=fs.end(); i!=e; ++i)
me.Eat(*i); //me tarzan! me eat!
Но снова, это не работает, и простое решение в C++, кажется, набор вызовов к dynamic_cast.
Однако как один из ответов предполагает, может быть другое умное решение. Что, если Фрукты выставили качества, которые имели значение для едоков с функциями как MustPeel () и MustWash ()? Затем Вы могли обойтись синглом, Едят () функцию...
Обновление: Daniel Newby указывает, что использующий Посетитель также решает проблему, как представлено..., но это требует определенной семантической стойки на голове (Фрукты:: использование или Фрукты:: избитый?).
В то время как я хотел бы принять несколько ответов, я думаю, что ответ psmears является на самом деле лучшим для будущих читателей. Спасибо, все.
Когда возникает подобный вопрос, полезно посмотреть, почему вы хотите принимать определенные решения - например, почему вы не хотите, чтобы классы Fruit знали о пестицидах?
Я уверен, что является веской причиной для этого, но выражение этой причины поможет прояснить в вашем уме, в чем именно заключаются ваши цели - и это часто проливает новый свет на возможный ракурс структурирования программы.
Например, вы можете добавить новые виртуальные методы «IsEdible» и «PrepareForEating». Затем вы можете реализовать их для каждого фрукта и реализовать один общий метод Eat, который работает для всех фруктов - и также принимает надоедливый пестицид - и все это без того, чтобы классы Fruit ничего об этом знали.
Конечно, в зависимости от ваших конкретных целей, это может быть совершенно неуместным - поэтому вам придется прояснить этот пример в своей голове: -)
Просто используйте "Я стою прямо здесь"! Шаблон. Это похоже на шаблон посетителя, но без контейнера.
// fruit.h
class Fruit;
class Apple;
class Orange;
class Fruit_user {
public:
Fruit_user();
virtual ~Fruit_user();
virtual use(Apple *f) = 0;
virtual use(Orange *f) = 0;
};
class Fruit {
public:
// Somebody with strong template fu could probably do
// it all here.
virtual void use(Fruit_user *fu) = 0;
};
class Apple : public Fruit {
public:
virtual void use(Fruit_user *fu) {
fu->use(this);
}
};
class Orange: public Fruit {
public:
virtual void use(Fruit_user *fu) {
fu->use(this);
}
};
// dow-chemical.h
class Pesticide_fruit_user : public Fruit_user {
public:
Pesticide_fruit_user(Pesticide *p) {
p_ = p;
}
virtual void use(Apple *f) { ingest(f, p_); }
virtual void use(Orange *f) { peel(f, p_); ingest(f, p_); }
private:
Pesticide *p_;
};
Нет ничего плохого в том, чтобы иметь произвольные указатели классов в заголовках. Они лежат в основе многих идиом, таких как PIMPL и непрозрачные указатели. Кроме того, если вы не вменяемый человек, как вы должны понять мой ответ?
Серьезно, производные функции и полиморфизм существуют для решения этой проблемы. Если вы отказываетесь использовать инструменты, предоставляемые языком, зачем вообще его использовать? Любое решение, которое вы можете придумать, в любом случае может быть переведено в вызов виртуальной функции, просто вы бы закодировали его вручную, а не поручили это компилятору.
Вам нужно переделать дизайн. А именно, сделать все то, чего вы, похоже, избегаете (по какой причине, кто знает)
Полиморфное поведение требует полиморфных функций. Это означает виртуальную
функцию. (Или ваша лестница из dynamic_cast
'ов, что полностью уничтожает цель...)
// fruit.h
class Pesticide; // you don't need a complete type
struct Fruit
{
virtual void Eat(Pesticide*) = 0;
};
// apple.h
class Apple : public Fruit
{
void Eat(Pesticide* p) { ... }
};
// orange.h
class Orange : public Fruit
{
void Eat(Pesticide* p) { ... }
};
Если вы все еще хотите свободную функцию*:
void Eat(Fruit* f, Pesticide* p) { f->Eat(p); }
* Обратите внимание, что ваш пост уже свидетельствует о плохом дизайне; а именно первая функция Eat
:
void Eat(Fruit* f, Pesticide* p) { }
Когда ничего не делать с фруктом приравнивается к поеданию фрукта? Чистая виртуальная функция - гораздо лучший выбор интерфейса.
То, о чем вы просите, невозможно. Разрешение перегрузки функции должно знать во время компиляции , к какому классу относится параметр, чтобы оно могло вызвать правильную функцию Eat
. Единственное исключение - виртуальные функции-члены, которые вы уже исключили.