2013-01-09 3 views
79

Ich möchte mehrere Typen haben, die die gleiche Implementierung teilen, aber immer noch von verschiedenen Typen in C++ sind.So definieren Sie verschiedene Typen für die gleiche Klasse in C++

Um meine Frage mit einem einfachen Beispiel zu illustrieren, hätte ich gerne eine Klasse für Äpfel, Orangen und Bananen, die alle dieselben Operationen und dieselbe Implementierung haben. Ich möchte, dass sie verschiedene Typen haben, weil ich Fehler vermeiden möchte, dank der Typ-Sicherheit.

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

Um nicht Code zu duplizieren, es sieht aus wie ich eine Basisklasse Obst verwenden könnte und erben von ihm:

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

Aber dann werden die Konstrukteure nicht geerbt, und ich habe sie neu zu schreiben.

Gibt es einen Mechanismus (typedefs, templates, Vererbung ...), der es mir erlauben würde, die gleiche Klasse mit verschiedenen Typen zu haben?

+0

Können Sie genauer erklären, warum würden Sie das brauchen? Ich kann mir keine gute Idee einfallen lassen. Wenn die Klassen die Implementierung teilen, bedeutet das nicht, dass sie auch die Funktionalität teilen? – jnovacho

+4

Ja, aber da sie unterschiedliche Typen haben, können einige Programmierfehler zum Zeitpunkt der Kompilierung entdeckt werden (zum Beispiel Apple und Orange zusammenführen). – anumi

Antwort

116

Eine übliche Technik ist eine Klasse-Vorlage zu haben, in dem die Vorlage Argument einfach als eindeutiges Token dient („Tag“) ist es eine einzigartige Art zu machen:

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

Beachten Sie, dass die Tag-Klassen muss nicht einmal definiert werden, es genügt, zu deklarieren einen eindeutigen Typnamen. Das funktioniert, weil das Tag isn's tatsächlich irgendwo in der Vorlage verwendet. Und Sie können den Typ-Namen innerhalb der Vorlage Argument Liste (Hut Tipp zu @ Xeo).

Die using-Syntax ist C++ 11. Wenn Sie mit C++ 03 stecken, schreiben Sie stattdessen:

typedef Fruit<struct AppleTag> Apple; 

Wenn die gemeinsame Funktionalität eine Menge Code in Anspruch nimmt dies leider ziemlich viel doppelten Code in den letzten ausführbaren einführt. Dies kann verhindert werden, indem eine gemeinsame Basisklasse implementiert wird, die die Funktionalität implementiert, und dann eine Spezialisierung hat (die Sie tatsächlich instanziieren), die daraus abgeleitet wird.

Das erfordert leider, dass Sie alle nicht vererbbaren Member (Konstruktoren, Zuweisung ...) neu implementieren, was einen kleinen Overhead selbst ergibt - das macht nur für große Klassen Sinn. Hier wird auf das obige Beispiel angewendet:

// 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>; 
+0

+1, würde ich mit diesem gehen, wenn Sie keine zusätzlichen Eigenschaften für die einzelnen Früchte definiert haben wollen ... – Nim

+19

Sie können sie eigentlich nur in der Template-Argumentliste deklarieren, was ich ziemlich praktisch finde: 'Fruit '. – Xeo

+3

@Xeo Was ist das für Magie? –

14
  • C++ 11 würde Konstruktor Vererbung erlauben: Using C++ base class constructors?
  • Andernfalls können Sie Vorlagen verwenden, das gleiche zu erreichen, z.B. template<class Derived> class Fruit;
19

Vorlagen verwenden, und verwenden Sie ein Merkmal pro Frucht, zum Beispiel:

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 

Dann haben eine einzelne Fruit Klasse, die auf dieser Eigenschaft eingegeben wird z.B.

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; 

Kann etwas Overkill sein!;)

Verwandte Themen