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?


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>;

+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! ;)



Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.