vendredi 13 août 2021

C++: How to implement a virtual function with different signatures?

What I want to do expose a name of member function to tell other developers that every derived class has to implement the specific function with the same name exposed in the interface, but the implemented function can use different signatures.

(Update: Add a simple example.)

Example:

class Fruit {};
class Meat {};
class Finger {};

class Animal {
 public:
  virtual void eat(/* something */) = 0;
};

class Monkey : public Animal {
 public:
  void eat(Fruit fruit, Finger finger) override {
    std::cout << "I eat fruits and suck my fingers" << std::endl;
  }
};

class Dog : public Animal {
 public:
  void eat(Meat meat) override {
    std::cout << "I eat meat" << std::endl;
  }
};

int main() {
  Meat meat;
  Fruit fruit;
  Finger finger;
  Monkey monkey;
  Dog dog;
  monkey.eat(fruit, finger);
  dog.eat(meat);
}

After reading:

I know there are two possible solution, one is using dispatcher, and the other is using CRTP.

Dispatcher: This solution breaks the type check.

class EatDispatcherInterface {
 public:
  virtual void eat(Monkey* monkey) = 0;
  virtual void eat(Dog* dog) = 0;
};

class EatDispatcher : public EatDispatcherInterface {
 public:
  EatDispatcher(Fruit fruit) : food(fruit) {}
  EatDispatcher(Meat meat) : food(meat) {}
  void eat(Monkey* monkey) override {
    std::cout << "I eat fruits" << std::endl;
  }
  void eat(Dog* dog) override {
    std::cout << "I eat meat" << std::endl;
  }
 private:
  union {
    Fruit fruit;
    Meat meat;
  } food;
};

class Fruit {};
class Meat {};

class Animal {
 public:
  virtual void accept(const EatDispatcher& eat_dispatcher) = 0;
};

class Monkey : public Animal {
 public:
  void eat(const EatDispatcher& eat_dispatcher) override {
    eat_dispatcher.eat(this);
  }
};

class Dog : public Animal {
 public:
  void eat(const EatDispatcher& eat_dispatcher) override {
    eat_dispatcher.eat(this);
  }
};

int main() {
  EatDispatcher fruit_eat_dispatcher(Fruit());
  EatDispatcher meat_eat_dispatcher(Meat());
  Monkey monkey;
  Dog dog;
  monkey.accept(fruit_eat_dispatcher);
  dog.accept(meat_eat_dispatcher);
}

CRTP: This solution exposes the detail of the derived class in the interface.

class Fruit {};
class Meat {};

template <typename Food>
class Animal {
 public:
  virtual void eat(Food food) = 0;
 };

class Monkey : public Animal<Fruit> {
 public:
  void eat(Fruit fruit) {
    std::cout << "I eat fruits" << std::endl;
  }
};

class Dog : public Animal<Meat> {
 public:
  void eat(Meat meat) {
    std::cout << "I eat meat" << std::endl;
  }
};

int main() {
  Monkey monkey;
  Dog dog;
  monkey.eat(Fruit());
  dog.eat(Meat());
}

Which is the proper pattern? Is there other solutions without these disadvantage?

Copyright 2021 Google LLC.

SPDX-License-Identifier: Apache-2.0

Aucun commentaire:

Enregistrer un commentaire