2017-11-29 18 views
4

次の例のように、クラスの型を定義するいくつかの列挙を有するクラスが与えられる:特定のメンバ変数の値とクラスの特殊タイプを作成

class Fruit { 
public: 

    enum class FruitType { 
     AppleType = 0, 
     OrangeType = 1, 
     BananaType = 2, 
    }; 
    Fruit(FruitType type) : type_(type) {} 
    FruitType fruit_type() const { return type_; } 

private: 
    FruitType type_; 
}; 

と同一を共有することから派生したクラスを列挙:

class DriedFruit : public Fruit { 
public: 
    // Some Dried specific methods. 
}; 

何とか特定の列挙値の各々と果物とDriedFruitための異なるタイプを定義することも可能である:

class Apple // Fruit with FruitType = AppleType 
class Orange // Fruit with FruitType = OrangeType 
class Banana // Fruit with FruitType = BananaType 
class DriedApple // DriedFruit with FruitType = AppleType 
class DriedOrange // DriedFruit with FruitType = OrangeType 
class DriedBanana // DriedFruit with FruitType = BananaType 

の3つのクラスがあり、Apple、Orange、Bananaの3つのクラスが区別され、3つのクラスDriedApple、DriedOrange、DriedBananaは異なるタイプです。

私の質問は、クラスの型の情報を明示的にクラスのenumメンバ変数として格納し、すべての個別の型に対して共通の基本クラスを持つことを除いて、How to define different types for the same class in C++と多少似ています。

これを行う最も効率的な方法は何でしょうか?

EDIT: 主な使用例は次のとおりです。私のアプリケーションでは、Appleが入力としてしか期待していない、またはオレンジを入力として期待する特定の方法があります。 。

アップルが期待している方法にフルーツを渡すのは不安ですが、同時に3つの異なる種類があることも気にしない多くの方法があります。 は、それがアップルである場合、アップル型をコンクリートに果実から変換 いくつかの時点で、次いで、フルーツとしてその周りに、いくつかの入力パラメータから次に パスフルーツを構築し、処理する次のよう

メインワークフローでありますそれをさらに処理し、その時点からAppleにタイプを制限します。

+0

あなたがリンクしている質問への回答は、この質問に答えませんか? –

+1

'type_'をprotectedにして、それぞれの派生クラスにコンストラクタで適切に設定されていることに何か間違っていますか? – zzxyz

+1

これは少しのXY問題に見えます。あなたは本当に 'フルーツ'の特定のインスタンスのための_distinct types_を持つ必要がありますか?共通クラスに実装された特殊なインターフェース?後者は設計上の瑕疵として匂いがする。 – user0042

答えて

3

これを行うにはどのような方法が最適でしょうか?

enum class FruitType { 
    AppleType = 0, 
    OrangeType = 1, 
    BananaType = 2, 
}; 

template <FruitType F> 
class Fruit { 
public: 
    FruitType fruit_type() const { return F; } 
}; 

using Apple = Fruit<FruitType::AppleType>; 
using Banana = Fruit<FruitType::BananaType>; 

あなたは、実際のベース・クラスはあなた次第です必要かどうか:

あなたは非型テンプレートパラメータを利用することができます。特定のFruitTypeに対してテンプレートの特殊化を提供するだけでも十分です。

+1

この回答はかなり良いですが、質問で尋ねられたように、 'Apple'と' Banana'は共通の基本型を共有していません。 –

+0

@FrançoisAndrieuxCRTPと継承を使用して簡単に解決できます。 – user0042

+0

@ user0042 CRTPは必要ありません。共通の基本クラスから継承するために 'Fruit'が必要です。 –

2

これはあなたのやりたいことですか?

enum class FruitType 
{ 
    AppleType = 0, 
    OrangeType = 1, 
    BananaType = 2, 
}; 

class Fruit 
{ 
public: 

    virtual FruitType fruit_type() const = 0; 
}; 

class Apple: public Fruit 
{ 
public: 

    FruitType fruit_type() const override { return FruitType::AppleType; } 
}; 

class Orange : public Fruit 
{ 
public: 

    FruitType fruit_type() const override { return FruitType::OrangeType; } 
}; 

class Banana : public Fruit 
{ 
public: 

    FruitType fruit_type() const override { return FruitType::BananaType; } 
}; 

int main() 
{ 
    Fruit *somefruit = new Apple; 

    std::cout << "Is Apple? " << std::boolalpha << (somefruit->fruit_type() == FruitType::AppleType) << std::endl; 
    std::cout << "Is Orange? " << std::boolalpha << (somefruit->fruit_type() == FruitType::OrangeType) << std::endl; 
    std::cout << "Is Banana? " << std::boolalpha << (somefruit->fruit_type() == FruitType::BananaType) << std::endl; 

    return 0; 
} 

プリント:

Is Apple? true 
Is Orange? false 
Is Banana? false 
+0

+1。ありがとうございます、それはうまくいくでしょうが、問題はFruitがすでに私のアプリケーションの基本クラスなので、私が避けたいと思っている多重継承につながるでしょう。 –

+3

@IlyaKobelevskiy私は、このアプローチがどのように複数の継承につながるかはわかりません。あなたは例を共有できますか?いずれにしても、この解決策に当てはまる場合は、考えられるすべての解決策に当てはまると思われます。多分私はあなたが何を意味するのか理解できませんでした。明確にするために、Bから継承し、BをCから継承することは多重継承ではなく、通常問題ではありません。 –

+0

@FrançoisAndrieux私のアプリケーションでは、FruitTypeから直交する概念のFruitから派生したDriedFruitもあります。私は、フルーツとドライフルーツのタイプのオブジェクトを処理する必要があります。ドライフルーツはフルーツと何か他のものです。このソリューションでは、Fruitが抽象化されるため、DriedFruitも抽象化されます。DriedApple、DriedOrange、およびDriedBananaを定義して、DriedFruitの具体的なインスタンスをインスタンス化できるようにする必要があります。 –

0

ご質問は要件について非常に抽象的です。

編集済みの明確化が

方法を指示しますが、メインユースケースは、次のようである - 私のアプリケーションでは、唯一の入力としてアップルを期待する特定の方法があります、または唯一の入力としてオレンジを期待し、何が果実であるか気にしない多くの方法。

私は(フルLive Demoを参照)をインタフェースとタグインタフェース
に基づいて完全に別のシステムを考えています。

最初のすべての果物のための共通のインタフェースを定義します。

// A basic interface common for all fruits 
struct IFruit { 
    virtual ~IFruit() {} 
    virtual std::string category() const = 0; 
    virtual std::string common_name() const = 0; 
    virtual std::string botanical_name() const = 0; 
}; 

// An overload for the output operator is just nifty 
std::ostream& operator<<(std::ostream& os, const IFruit& fruit) { 
    os << "Category  : " << fruit.category() << std::endl; 
    os << "Common Name : " << fruit.common_name() << std::endl; 
    os << "Botanical Name : " << fruit.botanical_name() << std::endl; 
    return os; 
} 

はあなたの特定の種類(りんご、オレンジ)を区別するためにタグインタフェースにを定義します。

// Tag interfaces to distinguish (not necessarily empty) 
struct IApple : public IFruit { 
    virtual ~IApple() {} 
}; 

struct IOrange : public IFruit { 
    virtual ~IOrange() {} 
}; 

これらはIFruitを実装するために必要とすべきです暗黙のうちにインターフェイス。


今、あなたはIFruitインタフェースを実装する抽象基本クラスを提供することができます。

// Abstract base class implementation 
template<class TagInterface> 
class FruitBase : public TagInterface { 
protected: 
     std::string category_; 
     std::string common_name_; 
     std::string botanical_name_; 

     FruitBase (const std::string& category 
       , const std::string& common_name 
       , const std::string botanical_name) 
      : category_(category), common_name_(common_name) 
      , botanical_name_(botanical_name) 
     {} 

public: 
     virtual ~FruitBase() {} 
     virtual std::string category() const { return category_; } 
     virtual std::string common_name() const { return common_name_; } 
     virtual std::string botanical_name() const { return botanical_name_; } 
}; 

としてを追加タグインターフェイスを追加:
この基底クラスは、コンストラクタ関数は、public範囲から隠されている意味で抽象があり、継承クラスのコンストラクタによって呼び出される必要があります必要に応じて:

struct IDriedApple : public IApple { 
    virtual ~IDriedApple() {} 
    virtual int rest_humidity() const = 0; 
}; 

は、今、あなたはかなり狭いクラスdefintionsを使用して、具体的な実装を作成することができます

// Concrete apples 
struct Boskop : public FruitBase<IApple> { 
public: 
    Boskop() : FruitBase("Apples","Boskop","Malus domestica 'Belle de Boskoop'") {} 
}; 

struct Braeburn : public FruitBase<IApple> { 
public: 
    Braeburn() : FruitBase("Apples","Braeburn","Malus domestica") {} 
}; 

// Concrete oranges 
struct Valencia : public FruitBase<IOrange> { 
public: 
    Valencia() : FruitBase("Oranges","Valencia","Citrus × sinensis") {} 
}; 

struct Navel : public FruitBase<IOrange> { 
public: 
    Navel() : FruitBase("Oranges","Navel","Citrus × sinensis") {} 
}; 

をここに私が想定し何あなたの関数の宣言だけりんごまたはオレンジを取るに特化しています:

void aFunctionThatTakesOnlyApples(IApple& anApple) { 
    std::cout << "This is an apple:" << std::endl; 
    std::cout << anApple; 
} 

void aFunctionThatTakesOnlyOranges(IOrange& anOrange) { 
    std::cout << "This is an orange:" << std::endl; 
    std::cout << anOrange << std::endl; 
} 

これは、の既知のインスタンスを照会する単純なテンプレート関数です特定のタグインターフェイスを実装するための: テンプレート TagInterface * queryTagInterface(IFruit * fruit){ return dynamic_cast(fruit); }


そして、これはあなたがアクションでそのすべてを使用する方法である:で、それが唯一のAppleを期待するメソッドにフルーツを渡すために/危険な曖昧な感じ

int main() { 
    std::vector<std::unique_ptr<IFruit>> allFruits; 
    allFruits.push_back(std::make_unique<Boskop>()); 
    allFruits.push_back(std::make_unique<Braeburn>()); 
    allFruits.push_back(std::make_unique<Valencia>()); 
    allFruits.push_back(std::make_unique<Navel>()); 
    for(auto& fruit : allFruits) { 
     if(IApple* anApple = queryTagInterface<IApple>(fruit.get())) { 
      aFunctionThatTakesOnlyApples(*anApple); 
     } 
     if(IOrange* anOrange = queryTagInterface<IOrange>(fruit.get())) { 
      aFunctionThatTakesOnlyOranges(*anOrange); 
     } 
     std::cout << "-----------------------------------------------" << std::endl; 
    }  
} 

同じ時間に、どのタイプに関わらず、3つの異なるタイプを持つことも良い選択肢ではない、多くの方法があります。

私はまたまだりんごオレンジを作るのか理解していないことに注意すべきであること、彼らが本当に自分のタイプに値する異なるフルーツ。しかし、それは、多態性クラス設計のあまり抽象的でないメタファーには適切であり、具体的なクラス階層設計には有用であるかもしれません。

関連する問題