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

5

5

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.

Schreiner answered 15/6, 2010 at 22:5 Comment(9)
Perhaps you should check yourself into the sanity hospital and make Eat a member function?Deboer
While C++ can give you information on the actual type through RTTI, I don't know of any way to force a cast to the actual subtype.Cypro
I'm not sure if what you want is multiple dispatch ( en.wikipedia.org/wiki/Multiple_dispatch ) or dynamic dispatch ( en.wikipedia.org/wiki/Dynamic_dispatch ).Margotmargrave
@Paul Nathan: Actually, it can make sense for eat() not to be a member. What if it depended on two polymorphic classes — Eater and Fruit? I'm not sure where I'd want it to reside in such case, but to me Eater looks like a more logical alternative.Illustrious
@doublep: true. Fruit typically doesn't eat itself. In a OO concept, the Animal class would have the Eat function, which is passed a Fruit.Deboer
@doublep @Paul Nathan: Yes, an Eater is a much better example than the plain old Eat functions. I will revise my example and vote you both up. @Juliano: That was helpful to read. I need multiple dispatch, which depends on a mechanism for dynamic dispatch. And which c++ apparently only supports with (surprise!) a ladder of dynamic_casts. Bonus points for you as well.Schreiner
Or maybe I won't. =/ I thought you could vote users up as well as questions and answers.Schreiner
Unless you plan on using null pointers, you should pass references, not pointers. Also note that you don't need Pesticide header in the Fruit header, merely a forward declaration.Tharpe
You make a good point about forward declarations, but I don't really want Pesticide in there at all. The Eater example is a clearer restatement of the problem.Schreiner
H
3

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 :-)

Hebbel answered 15/6, 2010 at 22:20 Comment(1)
Aha! So in addition to "dynamic_cast everything" and "have the fruit Eat itself", there is a third way: have the fruit expose enough information about itself that the Eater is able to know what to do with it, and have only one Eat() function. I like this answer.Schreiner
O
6

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.

Orlene answered 15/6, 2010 at 22:12 Comment(1)
Enh, I think it's just indicative of bad example ... here, I'll put in some magical ellipses.Schreiner
H
3

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 :-)

Hebbel answered 15/6, 2010 at 22:20 Comment(1)
Aha! So in addition to "dynamic_cast everything" and "have the fruit Eat itself", there is a third way: have the fruit expose enough information about itself that the Eater is able to know what to do with it, and have only one Eat() function. I like this answer.Schreiner
A
3

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_;
};
Avion answered 15/6, 2010 at 23:43 Comment(4)
Another good answer that provides another new solution for the problem presented in the original post. Using "I am standing right here" (like visitor) provides double-dispatch and gets the pesticide out of the header, all as requested.Schreiner
I must be getting dumber... but I see a very common Visitor here. en.wikipedia.org/wiki/Visitor_pattern What's the so fundamental difference ?Tharpe
Hm. I was thinking that Daniel meant that this is different because you only ever have one kind of visitor ... but even if that's right, I suppose it's just a particular way of using Visitor.Schreiner
The proper visitor traverses some kind of container, taking the visitor object to visit each containee, hence the name. Something has to take the worker to the data because it is traversal agnostic. In I Am Standing Right Here, the "visitor" already knows where the object is, but does not know what it is. (Note that the visitor pattern could be applied on top of my pattern. For example, to visit each fruit in a bunch of grapes.)Avion
D
0

There's nothing wrong with having arbitrary class pointers in headers. They form the basis of many idioms, like PIMPL and opaque pointers. Also, if you aren't a sane person, how are you supposed to understand my answer?

Seriously, derived functions and polymorphism exist to solve this problem. If you refuse to use the language provided tools, why bother using it at all? Any solution you can come up with can be translated into a virtual function call in any case, just you would have coded it manually instead of having the compiler do it.

Duplex answered 15/6, 2010 at 22:12 Comment(0)
L
0

What you're asking for isn't possible. The function overloading resolution needs to know at compile time which class the parameter is so it can call the correct Eat function. The only exception is for virtual member functions, which you've already ruled out.

Lapidary answered 15/6, 2010 at 22:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.