2010-06-15 2 views
5

Предположим, у меня есть куча фруктов:яблоки, апельсины, и указатели на наиболее производного класса 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); } 

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

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

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

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

Так что я действительно хочу, чтобы иметь возможность делать вместо этого:

Eat(*i); 

это:

Eat(MAGIC_CAST_TO_MOST_DERIVED_CLASS(*i)); 

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

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

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

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()? Затем вы могли бы пройти с помощью одной функции Eat() ...

Обновление: Daniel Newby указывает, что использование Visitor также решает проблему, как представлено ... но для этого требуется немного семантическая стойка (Fruit :: использование или фрукты :: beEaten?).

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

+0

Возможно, вам стоит зайти в санитарный госпиталь и принять функцию члена? –

+0

Хотя C++ может предоставить вам информацию о фактическом типе с помощью RTTI, я не знаю, каким образом заставить приведение к фактическому подтипу. – Jherico

+3

Я не уверен, что вы хотите отправить несколько сообщений (http://en.wikipedia.org/wiki/Multiple_dispatch) или динамическую отправку (http://en.wikipedia.org/wiki/Dynamic_dispatch). – Juliano

ответ

3

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

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

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

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

+0

Aha! Таким образом, помимо «dynamic_cast everything» и «есть плод, который есть сам», есть третий способ: есть плод, выставляющий достаточно информации о себе, что Eater может знать, что с ним делать, и иметь только один Eat (). Мне нравится этот ответ. –

6

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

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

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

+0

Enh, я думаю, это просто показатель плохого примера ... здесь, я поставлю некоторые магические эллипсы. –

0

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

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

0

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

3

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

// 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_; 
}; 
+0

Еще один хороший ответ, который предоставляет другое новое решение проблемы, представленное в исходном сообщении. Использование «Я стою прямо здесь» (например, посетитель) обеспечивает двойную отправку и выводит пестицид из заголовка, все по запросу. –

+0

Должно быть, я становлюсь глупее ... но я вижу здесь очень распространенного «Посетителя». http://en.wikipedia.org/wiki/Visitor_pattern В чем принципиальная разница? –

+0

Hm. Я думал, что Дэниел означал, что это другое, потому что у вас только один вид посетителя ... но даже если это правильно, я полагаю, что это просто особый способ использования Посетителя. –