Как определить разные типы для одного и того же класса в С++

Я хотел бы иметь несколько типов, которые используют одну и ту же реализацию, но все еще имеют разные типы в С++.

Чтобы проиллюстрировать мой вопрос простым примером, я хотел бы иметь класс для яблок, апельсинов и бананов, имеющих все те же операции и одну и ту же реализацию. Я бы хотел, чтобы у них были разные типы, потому что я хочу избежать ошибок благодаря безопасности типов.

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, templates, inheritance...), который позволит мне легко иметь один и тот же класс с разными типами?

Ответ 1

Общим методом является наличие шаблона класса, в котором аргумент шаблона просто служит уникальным токеном ( "тег" ), чтобы сделать его уникальным:

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

Обратите внимание, что классы тегов даже не нужно определять, достаточно, чтобы объявить уникальное имя типа. Это работает, потому что тег isns фактически используется в любом месте шаблона. И вы можете объявить имя типа внутри списка аргументов шаблона (tip tip to @Xeo).

Синтаксис using - это С++ 11. Если вы застряли с С++ 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>;

Ответ 2

Используйте шаблоны и используйте черту для каждого фрукта, например:

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;

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