Яблоки, апельсины и указатели на самый производный класс C ++ - PullRequest
5 голосов
/ 16 июня 2010

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

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); }

ОК, подождите.Остановись прямо там.Обратите внимание, что любой здравомыслящий человек сделает Eat () виртуальной функцией-членом классов Fruit.Но это не вариант, потому что я не вменяемый человек.Кроме того, я не хочу, чтобы Pesticide * в заголовочном файле для моего фруктового класса.

К сожалению, я хочу иметь возможность делать следующее: именно то, что позволяют функции-члены и динамическое связывание:

typedef list<Fruit*> Fruits;
Fruits fs;
...
for(Fruits::iterator i=fs.begin(), e=fs.end(); i!=e; ++i)
    Eat(*i);

И, очевидно, проблема здесь в том, что указатель, который мы передаем Eat (), будет Fruit *, а не Apple * или Orange *, поэтому ничего не съедается, и мы все будем очень голодны.

Итак, что я действительно хочу сделать вместо этого:

Eat(*i);

это:

Eat(MAGIC_CAST_TO_MOST_DERIVED_CLASS(*i));

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

Так есть ли какая-то магия времени выполнения, о которой я не знаю?Или я должен реализовать и поддерживать большое противное выражение if, полное dynamic_casts?Или я должен смириться с этим, перестать думать о том, как бы реализовать это в Ruby, и позволить небольшому количеству пестицидов проникнуть в мою колбу с фруктами?

Обновление: Вместо надуманногонемного с голыми функциями Eat и пестицидами, предположим, что вместо этого я просто не хочу употреблять Eat в фрукты, потому что это не имеет смысла.Фрукт, который умеет себя есть?Тьфу.Вместо этого мне нужен класс Eater с функцией Eat, с разным кодом для употребления каждого вида фруктов и кодом по умолчанию на тот случай, если этот фрукт не распознается едоком:

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.

Однако, как предполагает один из ответов, может быть другое умное решение.Что если Fruits продемонстрирует качества, которые имеют значение для едоков, с помощью таких функций, как MustPeel () и MustWash ()?Тогда вы могли бы обойтись с помощью одной функции Eat () ...

Обновление: Даниэль Ньюби отмечает, что использование Visitor также решает проблему, как показано ... но это требует немногосемантической стойки на голове (Fruit :: use или Fruit :: beEaten?).

Хотя я хотел бы принять несколько ответов, я думаю, что ответ psmears на самом деле лучший для будущих читателей.Спасибо всем.

Ответы [ 5 ]

6 голосов
/ 16 июня 2010

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

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

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

3 голосов
/ 16 июня 2010

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

// 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 голосов
/ 16 июня 2010

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

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

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

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

0 голосов
/ 16 июня 2010

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

0 голосов
/ 16 июня 2010

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

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

...