Search code examples
c++inheritancedesign-patternsabstract-classvirtual-functions

C++ Inheritance: arguments of derived class type in virtual function with base class types


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.

What is the best way to overcome this error?

So far I have found two options:

  1. Creating an 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.
  2. Turn the 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?


Solution

  • 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