Come definire diversi tipi per la stessa classe in C ++


84

Vorrei avere diversi tipi che condividono la stessa implementazione ma sono ancora di tipo diverso in C ++.

Per illustrare la mia domanda con un semplice esempio, vorrei avere una classe per mele, arance e banane, tutte con le stesse operazioni e la stessa implementazione. Vorrei che avessero tipi diversi perché voglio evitare errori grazie alla sicurezza dei tipi.

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

Per non duplicare il codice, sembra che potrei usare una classe base Fruit ed ereditarla da essa:

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

Ma poi, i costruttori non vengono ereditati e devo riscriverli.

Esiste un meccanismo (typedef, modelli, ereditarietà ...) che mi consentirebbe di avere facilmente la stessa classe con tipi diversi?


Puoi spiegare in modo più dettagliato perché avresti bisogno di questo? Non riesco a trovare una buona idea. Se le classi condividono l'implementazione, non significa che condividono anche le funzionalità?
jnovacho

4
Sì, ma poiché avranno tipi diversi, alcuni errori di programmazione possono essere rilevati in fase di compilazione (ad esempio, l'unione di mele e arance).
anumi

Risposte:


119

Una tecnica comune consiste nell'avere un modello di classe in cui l'argomento del modello serve semplicemente come un token univoco ("tag") per renderlo un tipo univoco:

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

Nota che le classi di tag non hanno nemmeno bisogno di essere definite, è sufficiente dichiarare un nome di tipo univoco. Questo funziona perché il tag non è effettivamente utilizzato ovunque nel modello. E puoi dichiarare il nome del tipo all'interno dell'elenco degli argomenti del modello (suggerimento a @Xeo).

La usingsintassi è C ++ 11. Se sei bloccato con C ++ 03, scrivi invece questo:

typedef Fruit<struct AppleTag> Apple;

Se la funzionalità comune richiede molto codice, questo purtroppo introduce un bel po 'di codice duplicato nell'eseguibile finale. Ciò può essere evitato disponendo di una classe base comune che implementa la funzionalità e quindi di una specializzazione (di cui si istanzia effettivamente) che ne deriva.

Sfortunatamente, ciò richiede di reimplementare tutti i membri non ereditabili (costruttori, assegnazioni ...) il che aggiunge un piccolo overhead stesso, quindi ha senso solo per classi grandi. Qui è applicato all'esempio sopra:

// 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, lo sceglierei se non si desidera avere ulteriori proprietà definite per i singoli frutti ...
Nim

20
Si può effettivamente semplicemente dichiarare all'interno all'elenco dei modelli argomento, che trovo abbastanza conveniente: Fruit<struct SomeTag>.
Xeo

1
@KonradRudolph è un peccato non posso fare +1 sulla modifica stessa ..... Ho visto quel commento di modifica .
eternalmatt

1
@eternalmatt LOL - Non avrei mai pensato che qualcuno l'avrebbe visto. Ma beh, devi essere divertente anche quando nessuno guarda. ;-)
Konrad Rudolph

2
Uno svantaggio di questo è l'emissione multipla dell'istanziazione del modello per i diversi tipi. Questi duplicati vengono eliminati dai linker ampiamente utilizzati?
boicottaggio

19

Usa modelli e usa un tratto per frutto, ad esempio:

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

Quindi avere una singola Fruitclasse che viene digitata su questo tratto, ad es.

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;

Può essere leggermente eccessivo! ;)



Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.