Chciałbym mieć kilka typów, które mają tę samą implementację, ale nadal są różnego typu w C ++.
Aby zilustrować moje pytanie prostym przykładem, chciałbym mieć klasę jabłek, pomarańczy i bananów, wszystkie z tymi samymi operacjami i taką samą implementacją. Chciałbym, żeby były różne typy, ponieważ chcę uniknąć błędów dzięki bezpieczeństwu typów.
class Apple {
int p;
public:
Apple (int p) : p(p) {}
int price () const {return p;}
}
class Banana {
int p;
public:
Banana (int p) : p(p) {}
int price () const {return p;}
}
class Orange ...
Aby nie powielać kodu, wygląda na to, że mógłbym użyć klasy bazowej Fruit i dziedziczyć po niej:
class Fruit {
int p;
public:
Fruit (int p) : p(p) {}
int price () const {return p;}
}
class Apple: public Fruit {};
class Banana: public Fruit {};
class Orange: public Fruit {};
Ale wtedy konstruktorzy nie są dziedziczeni i muszę je przepisać.
Czy istnieje mechanizm (typy typów, szablony, dziedziczenie ...), który pozwoliłby mi łatwo mieć tę samą klasę z różnymi typami?
Odpowiedzi:
Typową techniką jest posiadanie szablonu klasy, w którym argument szablonu służy po prostu jako unikalny token („tag”), aby nadać mu unikalny typ:
template <typename Tag> class Fruit { int p; public: Fruit(int p) : p(p) { } int price() const { return p; } }; using Apple = Fruit<struct AppleTag>; using Banana = Fruit<struct BananaTag>;
Zwróć uwagę, że klasy tagów nie muszą nawet być definiowane, wystarczy zadeklarować unikalną nazwę typu. To działa, ponieważ tag nie jest faktycznie używany w żadnym miejscu w szablonie. I możesz zadeklarować nazwę typu wewnątrz listy argumentów szablonu (wskazówka do @Xeo).
using
Składni C ++ 11. Jeśli utkniesz z C ++ 03, napisz to:typedef Fruit<struct AppleTag> Apple;
Jeśli wspólna funkcjonalność zajmuje dużo kodu, niestety wprowadza to sporo zduplikowanego kodu w końcowym pliku wykonywalnym. Można temu zapobiec, mając wspólną klasę bazową implementującą tę funkcjonalność, a następnie specjalizację (którą faktycznie tworzysz), która z niej pochodzi.
Niestety, wymaga to ponownego zaimplementowania wszystkich niedziedziczych elementów członkowskich (konstruktorów, przypisania…), co samo w sobie dodaje niewielki narzut - więc ma to sens tylko w przypadku dużych klas. Tutaj odnosi się to do powyższego przykładu:
// Actual `Fruit` class remains unchanged, except for template declaration template <typename Tag, typename = Tag> class Fruit { /* unchanged */ }; template <typename T> class Fruit<T, T> : public Fruit<T, void> { public: // Should work but doesn’t on my compiler: //using Fruit<T, void>::Fruit; Fruit(int p) : Fruit<T, void>(p) { } }; using Apple = Fruit<struct AppleTag>; using Banana = Fruit<struct BananaTag>;
źródło
Fruit<struct SomeTag>
.Użyj szablonów i użyj cechy dla każdego owocu, na przykład:
struct AppleTraits { // define apple specific traits (say, static methods, types etc) static int colour = 0; }; struct OrangeTraits { // define orange specific traits (say, static methods, types etc) static int colour = 1; }; // etc
Następnie miej jedną
Fruit
klasę, która jest wpisana na tę cechę, np.template <typename FruitTrait> struct Fruit { // All fruit methods... // Here return the colour from the traits class.. int colour() const { return FruitTrait::colour; } }; // Now use a few typedefs typedef Fruit<AppleTraits> Apple; typedef Fruit<OrangeTraits> Orange;
Może to być trochę przesada! ;)
źródło
template<class Derived> class Fruit;
źródło
Jest też BOOST_STRONG_TYPEDEF .
źródło