如何在 C + + 中为同一个类定义不同的类型

我希望有几种类型共享相同的实现,但在 C + + 中仍然是不同的类型。

为了用一个简单的例子来说明我的问题,我想为 Apple、 Orange 和 Bananas 创建一个类,它们都具有相同的操作和相同的实现。我希望他们有不同的类型,因为我想避免由于类型安全的错误。

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

但是,构造函数不是继承的,我必须重写它们。

是否有任何机制(typedef、模板、继承... ...)允许我轻松地使用不同类型的相同类?

9429 次浏览
  • C++11 would allow constructor inheritance: Using C++ base class constructors?
  • Otherwise, you can use templates to achieve the same, e.g. template<class Derived> class Fruit;

A common technique is to have a class template where the template argument simply serves as a unique token (“tag”) to make it a unique type:

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

Note that the tag classes don’t even need to be defined, it’s enough to declare a unique type name. This works because the tag isn’s actually used anywhere in the template. And you can declare the type name inside the template argument list (hat tip to @Xeo).

The using syntax is C++11. If you’re stuck with C++03, write this instead:

typedef Fruit<struct AppleTag> Apple;

If the common functionality takes up a lot of code this unfortunately introduces quite a lot of duplicate code in the final executable. This can be prevented by having a common base class implementing the functionality, and then having a specialisation (that you actually instantiate) that derives from it.

Unfortunately, that requires you to re-implement all non-inheritable members (constructors, assignment …) which adds a small overhead itself – so this only makes sense for large classes. Here it is applied to the above example:

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

Use templates, and use a trait per fruit, for example:

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

Then have a single Fruit class which is typed on this trait eg.

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;

May be slightly overkill! ;)