I'm having a rough time with a particular C++ inheritance problem. Say we have two abstract classes, one using the other as argument type for one of the pure virtual functions:
class Food {
public:
int calories=0;
virtual void set_calories(int cal)=0;
}
class Animal {
public:
int eaten_calories=0;
virtual void eat_food(Food &f)=0;
}
Now, we create a derived class for each, and we instantiate a virtual function with arguments of type the derived class:
class Vegetables: public Food{
public:
void set_calories(int cal){calories=cal;}
}
class Cow: public Animal{
public:
void eat_food(Vegetables &v){this->eaten_calories += v.calories;}
}
The problem with this is that the function eat_food
requires a signature with the abstract class Food
, or else a Cow()
object creation won't compile, complaining that Cow
is an abstract class because no suitable implementation of eat_food(Food f)
was found.
Update: An additional constraint I seek for the implementation is that a second class Meat: public Food
should not be usable with Cow::eat_food(f)
. In short, just setting Cow::eat_food(Food f)
and casting to Vegetables
wouldn't cut it.
So far I have found two options:
eat_food(Food f)
implementation in Cow
with a try/catch
to check if f
can be safely casted to Vegetables
, and then calling eat_food(Vegetables v)
. PROBLEM: if you have 50 virtual functions, this forces you to write 50 additional function implementations in Cow
.Animal
into a Template class Animal<T>
, and instantiate it with each of the derived classes of Food
to define the animals (e.g., class Cow: public Animal<Vegetables>
). PROBLEM: you can no longer define an Animal*
pointer to hold an undefined animal with not known type.Is there any viable/stylish alternative to these two? Maybe a software pattern of some kind?
If you pass around a polymorphic type (like Vegetables
) as a base type by value (like Food f
), you will slice the object, which prevents overriden methods from being called.
You need to pass such types by pointer or by reference instead, eg:
class Food {
public:
virtual int get_calories() const = 0;
};
class Animal {
public:
int eaten_calories = 0;
virtual void eat_food(Food& f) = 0;
};
class Vegetables: public Food {
public:
int get_calories() const { return ...; }
};
class Cow: public Animal{
public:
void eat_food(Food& f){ this->eaten_calories += f.get_calories(); }
};
Vegetables veggies;
Cow cow;
cow.eat_food(veggies);
UPDATE:
You can't change the signature of a virtual method in derived classes (except when using covariant return types). Since eat_food()
is exposed in Animal
and takes a Food&
, if you want Cow::eat_food()
to accept only a Vegetables
object and not a Meat
object, then it needs to check at runtime if the input Food&
refers to a Vegetables
object and if not then throw an exception. dynamic_cast
does exactly that for you when casting a reference, eg:
class Cow: public Animal{
public:
void eat_food(Food& f){ this->eaten_calories += dynamic_cast<Vegetables&>(f).calories; }
};
Vegetables veggies;
Meat meat;
Cow cow;
cow.eat_food(veggies); // OK
cow.eat_food(meat); // throws std::bad_cast