Jak zdefiniować różne typy dla tej samej klasy w C ++

84

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?

anumi
źródło
Czy możesz wyjaśnić bardziej szczegółowo, dlaczego miałbyś tego potrzebować? Nie mam dobrego pomysłu. Jeśli klasy mają wspólną implementację, czy nie oznacza to, że mają również wspólną funkcjonalność?
jnovacho
4
Tak, ale ponieważ będą miały różne typy, niektóre błędy programistyczne można wykryć w czasie kompilacji (na przykład łączenie jabłek i pomarańczy).
anumi

Odpowiedzi:

119

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

usingSkł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>;
Konrad Rudolph
źródło
+1, pójdę z tym, jeśli nie chcesz mieć żadnych dodatkowych właściwości zdefiniowanych dla poszczególnych owoców ...
Nim
20
Rzeczywiście można po prostu zadeklarować je wewnątrz listy szablon argumentów, która mi się znaleźć bardzo wygodne: Fruit<struct SomeTag>.
Xeo,
1
@KonradRudolph szkoda, że ​​nie mogę dać +1 samej edycji ..... Widziałem ten komentarz edycji .
eternalmatt
1
@eternalmatt LOL - Nigdy bym nie pomyślał, że ktokolwiek to zobaczy. Ale cóż, musisz być zabawny, nawet jeśli nikt nie patrzy. ;-)
Konrad Rudolph
2
Wadą tego jest wielokrotna emisja instancji szablonu dla różnych typów. Czy te duplikaty są eliminowane przez powszechnie używane linkery?
boycy
19

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ą Fruitklasę, 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! ;)

Nim
źródło