Как определить разные типы для одного и того же класса в 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 {};

Но тогда конструкторы не наследуются, и мне приходится их переписывать.

Есть ли какой-либо механизм (определения типов, шаблоны, наследование ...), который позволил бы мне легко иметь один и тот же класс с разными типами?

ануми
источник
Не могли бы вы подробнее объяснить, зачем вам это нужно? Я не могу придумать ничего хорошего. Если классы имеют общую реализацию, не означает ли это, что они также имеют общую функциональность?
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>;

Обратите внимание, что классы тегов даже не нужно определять, достаточно объявить уникальное имя типа. Это работает, потому что тег фактически не используется где-либо в шаблоне. И вы можете объявить имя типа внутри списка аргументов шаблона (подсказка к @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 к самому редактированию ..... Я видел этот комментарий редактирования .
eternalmatt
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;

Может быть немного перебор! ;)

Ним
источник