C ++で同じクラスに異なる型を定義する方法


84

同じ実装を共有しているが、C ++ではまだ異なるタイプのいくつかのタイプが欲しいのですが。

簡単な例で私の質問を説明するために、すべて同じ操作と同じ実装を持つApple、Oranges、および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、テンプレート、継承...)はありますか?


なぜこれが必要なのか、詳しく説明していただけますか?私は良い考えを思い付くことができません。クラスが実装を共有している場合、それはそれらが機能も共有していることを意味しませんか?
jnovacho 2013年

4
はい。ただし、タイプが異なるため、コンパイル時にいくつかのプログラミングエラーを検出できます(たとえば、AppleとOrangesのマージ)。
anumi 2013年

回答:


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、個々の果物に追加のプロパティを定義したくない場合は、これを使用します...
Nim

20
実際には、テンプレート引数リスト内でそれらを宣言することができますFruit<struct SomeTag>。これは非常に便利です。
xeo 2013年

1
@KonradRudolph編集自体を+1できないのは残念です.....その編集コメントを見ました。
eternalmatt 2013年

1
@eternalmatt LOL –誰もがそれを見るとは思ってもみませんでした。しかし、まあ、誰も見ていないときでも、あなたは面白くなければなりません。;-)
Konrad Rudolph

2
これの欠点は、さまざまなタイプのテンプレートインスタンス化の複数の放出です。これらの重複は、広く使用されているリンカーによって排除されていますか?
ボーイシー2013年

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;

少しやり過ぎかもしれません!;)



弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.