Как определить разные типы для одного и того же класса в С++
Я хотел бы иметь несколько типов, которые используют одну и ту же реализацию, но все еще имеют разные типы в С++.
Чтобы проиллюстрировать мой вопрос простым примером, я хотел бы иметь класс для яблок, апельсинов и бананов, имеющих все те же операции и одну и ту же реализацию. Я бы хотел, чтобы у них были разные типы, потому что я хочу избежать ошибок благодаря безопасности типов.
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;
Может быть немного переборщить!;)
Ответ 3
Ответ 4
Существует также BOOST_STRONG_TYPEDEF.