Як визначити різні типи для одного класу в C ++


84

Я хотів би мати декілька типів, які мають однакову реалізацію, але все ще мають різний тип в C ++.

Щоб проілюструвати своє запитання на простому прикладі, я хотів би мати клас для яблук, апельсинів та бананів, у яких однакові операції та однакова реалізація. Я хотів би, щоб вони мали різні типи, оскільки я хочу уникати помилок завдяки безпеці типу.

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

Щоб не дублювати код, схоже, я міг би використовувати базовий клас Fruit і успадкувати від нього:

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

Але тоді конструктори не успадковуються, і я повинен їх переписати.

Чи існує якийсь механізм (typedefs, шаблони, успадкування ...), який би дозволив мені легко мати той самий клас з різними типами?


Чи можете ви пояснити детальніше, навіщо це потрібно? Я не можу придумати жодної гарної ідеї. Якщо класи мають спільну реалізацію, чи не означає це, що вони також мають спільну функціональність?
jnovacho

4
Так, але оскільки вони матимуть різні типи, деякі помилки програмування можна виявити під час компіляції (наприклад, злиття яблук та апельсинів).
anumi

Відповіді:


119

Поширеною технікою є наявність шаблону класу, де аргумент шаблону просто служить унікальним маркером (“тегом”), щоб зробити його унікальним типом:

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

Зауважте, що класи тегів навіть не потрібно визначати, досить оголосити унікальне ім’я типу. Це працює, оскільки тег isn фактично використовується в будь-якому місці шаблону. І ви можете оголосити ім'я типу всередині списку аргументів шаблону (підказка до @Xeo).

usingСинтаксис C ++ 11. Якщо ви застрягли в C ++ 03, напишіть замість цього:

typedef Fruit<struct AppleTag> Apple;

Якщо загальна функціональність займає багато коду, це, на жаль, вносить досить багато дублікатів коду в остаточний виконуваний файл. Цього можна запобігти, якщо загальний базовий клас реалізує функціонал, а потім має спеціалізацію (яку ви фактично створюєте), яка з неї походить.

На жаль, для цього потрібно повторно реалізувати всі не успадковувані члени (конструктори, призначення…), що додає самі невеликі накладні витрати - тому це має сенс лише для великих класів. Тут це застосовано до наведеного вище прикладу:

// 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, я б погодився з цим, якщо ви не хочете, щоб були визначені додаткові властивості для окремих фруктів ...
Нім

20
Ви можете на самому справі просто оголосити їх в списку аргументів шаблону, який я знаходжу дуже зручно: Fruit<struct SomeTag>.
Xeo

1
@KonradRudolph соромно, що я не можу поставити +1 самому редагуванню ..... Я бачив цей коментар до редагування .
forevermatt

1
@eternalmatt LOL - я ніколи не міг подумати, що хтось це побачить. Але добре, ти повинен бути смішним, навіть коли ніхто не дивиться. ;-)
Конрад Рудольф

2
Недоліком цього є багаторазове випромінювання екземпляра шаблону для різних типів. Чи усуваються ці дублікати широко використовуваними лінкерами?
boycy

19

Використовуйте шаблони та використовуйте ознаку для кожного фрукта, наприклад:

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

Тоді мати один Fruitклас, який набирається за цією ознакою, наприклад.

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;

Може бути трохи надмірно! ;)



Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.