Яблоки, апельсины и указатели на наиболее полученный класс C++

Предположим, что у меня есть набор фруктов:

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 является на самом деле лучшим для будущих читателей. Спасибо, все.

5
задан Matthew Lowe 16 June 2010 в 14:04
поделиться

5 ответов

Когда возникает подобный вопрос, полезно посмотреть, почему вы хотите принимать определенные решения - например, почему вы не хотите, чтобы классы Fruit знали о пестицидах?

Я уверен, что является веской причиной для этого, но выражение этой причины поможет прояснить в вашем уме, в чем именно заключаются ваши цели - и это часто проливает новый свет на возможный ракурс структурирования программы.

Например, вы можете добавить новые виртуальные методы «IsEdible» и «PrepareForEating». Затем вы можете реализовать их для каждого фрукта и реализовать один общий метод Eat, который работает для всех фруктов - и также принимает надоедливый пестицид - и все это без того, чтобы классы Fruit ничего об этом знали.

Конечно, в зависимости от ваших конкретных целей, это может быть совершенно неуместным - поэтому вам придется прояснить этот пример в своей голове: -)

3
ответ дан 18 December 2019 в 16:36
поделиться

Просто используйте "Я стою прямо здесь"! Шаблон. Это похоже на шаблон посетителя, но без контейнера.

// 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_;
};
3
ответ дан 18 December 2019 в 16:36
поделиться

Нет ничего плохого в том, чтобы иметь произвольные указатели классов в заголовках. Они лежат в основе многих идиом, таких как PIMPL и непрозрачные указатели. Кроме того, если вы не вменяемый человек, как вы должны понять мой ответ?

Серьезно, производные функции и полиморфизм существуют для решения этой проблемы. Если вы отказываетесь использовать инструменты, предоставляемые языком, зачем вообще его использовать? Любое решение, которое вы можете придумать, в любом случае может быть переведено в вызов виртуальной функции, просто вы бы закодировали его вручную, а не поручили это компилятору.

0
ответ дан 18 December 2019 в 16:36
поделиться

Вам нужно переделать дизайн. А именно, сделать все то, чего вы, похоже, избегаете (по какой причине, кто знает)

Полиморфное поведение требует полиморфных функций. Это означает виртуальную функцию. (Или ваша лестница из 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)   { }

Когда ничего не делать с фруктом приравнивается к поеданию фрукта? Чистая виртуальная функция - гораздо лучший выбор интерфейса.

6
ответ дан 18 December 2019 в 16:36
поделиться

То, о чем вы просите, невозможно. Разрешение перегрузки функции должно знать во время компиляции , к какому классу относится параметр, чтобы оно могло вызвать правильную функцию Eat . Единственное исключение - виртуальные функции-члены, которые вы уже исключили.

0
ответ дан 18 December 2019 в 16:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: