Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apples, oranges, and pointers to the most derived c++ class

Suppose I have a bunch of fruit:

class Fruit { ... };
class Apple : public Fruit { ... };
class Orange: public Fruit { ... };

And some polymorphic functions that operate on said 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, wait. Stop right there. Note at this point that any sane person would make Eat() a virtual member function of the Fruit classes. But that's not an option, because I am not a sane person. Also, I don't want that Pesticide* in the header file for my fruit class.

Sadly, what I want to be able to do next is exactly what member functions and dynamic binding allow:

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

And obviously, the problem here is that the pointer we pass to Eat() will be a Fruit*, not an Apple* or an Orange*, therefore nothing will get eaten and we will all be very hungry.

So what I really want to be able to do instead of this:

Eat(*i);

is this:

Eat(MAGIC_CAST_TO_MOST_DERIVED_CLASS(*i));

But to my limited knowledge, such magic does not exist, except possibly in the form of a big nasty if-statement full of calls to dynamic_cast.

So is there some run-time magic of which I am not aware? Or should I implement and maintain a big nasty if-statement full of dynamic_casts? Or should I suck it up, quit thinking about how I would implement this in Ruby, and allow a little Pesticide to make its way into my fruit header?

Update: Instead of the contrived bit with the bare Eat functions and Pesticide, suppose instead that I just don't want to put Eat in the fruit because it makes no sense. A fruit that knows how to eat itself? Pshaw. Instead I need an Eater class with an Eat function, with different code for eating each kind of fruit, and some default code in case it's a fruit that the eater doesn't recognize:

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!

But again, this doesn't work, and the straightforward solution in C++ seems to be a bunch of calls to dynamic_cast.

However, as one of the answers suggests, there may be another clever solution. What if Fruits exposed the qualities that mattered to eaters, with functions like MustPeel() and MustWash()? Then you could get by with a single Eat() function ...

Update: Daniel Newby points out that using Visitor also solves the problem as presented ... but this requires a bit of a semantic headstand (Fruit::use or Fruit::beEaten?).

While I'd like to accept several answers, I think psmears's answer is actually the best one for future readers. Thanks, everyone.

like image 777
Matthew Lowe Avatar asked Jun 15 '10 22:06

Matthew Lowe


3 Answers

You need to redesign. Namely, do everything you seem to be avoiding (for what reason, who knows.)

Polymorphic behavior requires polymorphic functions. This means a virtual function. (Or your ladder of dynamic_cast's, which completely defeats the purpose...)

// 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) { ... }
};

If you still want a free function*:

void Eat(Fruit* f, Pesticide* p)   { f->Eat(p); }

*Note that your post is already indicative of bad design; namely the first Eat function:

void Eat(Fruit* f, Pesticide* p)   { }

When does doing nothing to a fruit equate to eating the fruit? A pure virtual function is a much better interface choice.

like image 184
GManNickG Avatar answered Nov 12 '22 17:11

GManNickG


When a question like this comes up, it's good to look at exactly why you want to make particular decisions - for instance, why do you not want the Fruit classes to know about Pesticide?

I'm sure there is a good reason for this - but expressing that reason will help clarify in your mind exactly what your aims are - and this often sheds a new light on a possible angle for structuring the program.

For instance, you might end up adding new virtual methods "IsEdible" and "PrepareForEating". Then you can implement these for each fruit, and implement one generic Eat method that works for all fruits - and ingests the pesky pesticide too - all without the Fruit classes knowing anything about it.

Of course, depending on your precise aims, that may be totally inappropriate - which is why you'll have to clarify the example in your own head :-)

like image 3
psmears Avatar answered Nov 12 '22 19:11

psmears


Just use the I Am Standing Right Here! Pattern. It's like the Visitor Pattern but without a container.

// 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_;
};
like image 3
Daniel Newby Avatar answered Nov 12 '22 19:11

Daniel Newby