2016-04-20 22 views
0

私は現在、独自のエンティティコンポーネントアーキテクチャを作成しようとしていますが、いくつか問題があります。サブタイプに基づいて動的にC++の構造体をキャスト

私は私のシステムは継承をvaringの構造体を保存できるようにしたいと思います例えば

struct BaseComponent 
{ 
    bool isValid; 
} 

struct ComponentA : BaseComponent 
{ 
    int someValForA; 
} 

struct ComponentB : BaseComponent 
{ 
    int someValForB 
} 

ComponentB* compB = new ComponentB() 
compB->someValForB = 10; 
BaseComponent* baseComp = compB 

ComponentB* comp = (ComponentB*) baseComp 

、構造体としての私のコンポーネントを格納しています。だから私はポインタのベクトルを使用する必要があります。問題は、元のサブタイプを知らなくても、元の派生構造体に動的にキャストする方法です。列挙型ではなく、コードで派生した型を調べることはできますか?これはライブラリで実装したいからです。

私はまた、このシステムを実装するための別のアプローチを提供し、私がそれを開発したいと心に留めておきます。可能であれば、具体的にコード例を挙げてください。

読んでいただきありがとうございます。

PS。これは私が今日アップロードした別の質問の再投稿です。重複した質問としてクローズされましたが、私の質問に答えることさえできませんでした。他の誰かが助けてくれるのを防ぐ代わりに、私があなたのコメントの正確さを理解することを要請します。ありがとう。お使いのベースクラスが多型である場合

+0

こんにちは、私はいくつかの時間に同じ問題を抱えていました。しかし、あなたが言ったように、私はいつもBaseComponentに "type"を追加して列挙し、subClass(またはsubStruct)を構築する際に特定の型に初期化することになりました...動的に型を取得することはできません – Stefano

+0

ええ、それは私のバックアップ計画ですしかし、別の方法がある場合、IDは知っているが、あなたのコメントのためにとにかくあなたのコメントのためにたくさんありがとう:) –

+0

javaで参照親クラスが派生クラスのインスタンスであるかどうかを確認することができます。 –

答えて

2

タイプ情報を投げ捨てるのではなく、すべてのコンポーネントがあなたが見ているコンポーネントかどうかをチェックするのではなく、タイプをコンポーネントにマップするコンテナを使用することをお勧めします。

using TypeId = unsigned int; 

namespace detail_typeId { 
    TypeId idCounter = 0u; 
} 

template <class T> 
TypeId const idFor = detail_typeId::idCounter++; 

このトリックは、キーとして使用することができる各タイプの一意の識別子の値を提供するために、idFor<T>の特殊化の初期に副作用を使用します。 std::type_indexを使用することもできますが、そのために多態性クラスをコンポーネントとして使用する必要があります。このアプローチには、連続整数識別子を生成する利点もあり、範囲は[0, idCounter - 1]です。

struct Component {}; 

コンポーネントの基本クラスです。

struct Entity { 

    template <class T, class... Args> 
    void addComponent(Args &&... args) { 
     if(!comps.emplace(
      idFor<T>, std::make_unique<T>(std::forward<Args>(args)...) 
     ).second) 
      throw std::runtime_error("Component already present."); 
    } 

    template <class T> 
    T *getComponent() { 
     auto found = comps.find(idFor<T>); 
     return found == end(comps) 
      ? nullptr 
      : static_cast<T*>(found->second.get()); 
    } 

    std::map<TypeId, std::unique_ptr<Component>> comps; 
}; 

コンポーネントの実際の格納場所と、それらにアクセスする2つの便利な機能があります。マップを使用すると、そのタイプに基づいてコンポーネントを取得できます。3ユーザ定義コンポーネントと

使用例:

struct CompA : Component { int attribA; }; 
struct CompB : Component { int attribB; }; 
struct CompC : Component { int attribC; }; 

int main() { 
    Entity e; 
    e.addComponent<CompA>(); 
    e.addComponent<CompB>(); 

    if(CompA *c = e.getComponent<CompA>()) { 
     std::cout << "Retrieved component A\n"; 
     c->attribA = 42; 
    } 

    if(CompB *c = e.getComponent<CompB>()) { 
     std::cout << "Retrieved component B\n"; 
     c->attribB = 42; 
    } 

    if(CompC *c = e.getComponent<CompC>()) { 
     std::cout << "Retrieved component C\n"; 
     c->attribC = 42; 
    } else { 
     std::cout << "Didn't retrieve component C\n"; 
    } 
} 

は出力:

Retrieved component A 
Retrieved component B 
Didn't retrieve component C

Live on Coliru

+0

Omgそれをカバーする多くのことがあり、あらゆる面で私を助けます!!!どうもありがとうございます!!!エンティティベースのコンポーネントを学びたいと思っている他の人には、これが必要なものであり、見つけられなかったので、これを見つけることを願っています。華麗な説明。ありがとうございました –

+0

私の質問に答えましたが、別の速いものとして "template void addComponent(Args && ... args){"私はあまりにもテンプレートに慣れていませんが、究極の理解とそれに関する研究を計画していますが、 "Class ... Args"は何をしていますか? –

+0

@ZacShepherd 'class ... Args'はバリデーションテンプレートパラメータパック*です。 'Args && ...'と 'std :: forward'の組み合わせは* perfect forwarding *と呼ばれ、' addComponent'関数は任意の数の引数を取ることができ、 'std: :make_unique'。あなたが 'std :: make_unique'に慣れていない場合、それらは' CompX'のコンストラクタのパラメータです。しかし、 'getComponent'は' std :: unique_ptr 'を取り込み、呼び出し側に自分自身で構築させることができます。 – Quentin

2

、あなたは(Javaでinstanceofのようなビット)、元に戻って変換することdynamic_castを使用することができます。そして、

struct Base { 
    // We need this or any other virtual member to make Base polymorphic 
    virtual ~Base() { } 
}; 

struct Derived1: public Base { 
    void foo() { 
     std::cout << "foo" << std::endl; 
    } 
}; 

struct Derived2: public Base { 
    void bar() { 
     std::cout << "bar" << std::endl; 
    } 
}; 

あなたは以下のクラスがあるとしましょうあなたは(Baseのデストラクタが、これが正しく動作するためにはvirtualする必要があります)Base*vectorでこれらの値を保存することができます。

std::vector<Base*> bases; 
bases.push_back(new Derived1()); 
bases.push_back(new Derived2()); 
bases.push_back(new Derived2()); 
bases.push_back(new Derived1()); 

その後、dynamic_castを使用してバック派生クラスに取得することができます:キャストが失敗した場合に呼び出すことはありませんので、もともとDerived2*d->foo()pbase場合

for (auto pbase: bases) { 
    if (Derived1 *d = dynamic_cast<Derived1*>(pbase)) { 
     d->foo(); 
    } 
    if (Derived2 *d = dynamic_cast<Derived2*>(pbase)) { 
     d->bar(); 
    } 
} 

dynamic_cast意志は、NULLポインタを返すので、それはあります安全。

Baseが多型でない場合(virtualを削除してみてください)、dynamic_cast(コンパイラエラー)は使用できません。

代わりに生のポインタBase*を使用しての、あなたが(例えばstd::shared_ptr)手動でメモリを解放することを避けるために、スマートポインタを使用して(必要がある)可能性があり注意。これを行う場合、dynamic_castの代わりにdynamic_pointer_castを使用する必要があります。

+0

ありがとうございました。多くの人が投稿した後、私はこれを回答として受け入れます。また、私はdynamic_castを聞いたことがあり、dynamic_pointer_castはstatic_castよりも重く描いていると思います。私はこれの程度に慣れていないと私は私のアルゴリズムに考慮する必要がありますか? –

+0

'static_cast'はキャストが有効かどうかをチェックしますが、' static_cast'はそうではありませんので、速度は遅くなりますが、あなたが欲しいことをするための唯一の方法です(static_castを使うと、 )。 – Holt

+0

これで、メソッドの余分な重量は安全だからです...これは安全なコードのために引き戻すことができるようなものです...ありがとう^ _^ –

0

このようにすることもできます。それはホルトの答えほど専門的ではありませんが、同じ欠点を共有しています。後で詳しく説明します。

#include <iostream> 
#include <memory> 

struct Base { 
     virtual int what() const = 0; 
     virtual ~Base(){} 
}; 

struct Derived1 : Base { 
     static constexpr int ME = 1; 

     int what() const{ 
       return ME; 
     } 
}; 

struct Derived2 : Base { 
     static constexpr int ME = 2; 

     int what() const{ 
       return ME; 
     } 
}; 

using pBase = std::unique_ptr<Base>; 

void doSomething(pBase &base){ 
     switch(base->what()){ 
     case Derived1::ME : 
       std::cout << "Derived1" << std::endl; 
       break; 

     case Derived2::ME : 
       std::cout << "Derived2" << std::endl; 
       break; 

     default: 
       std::cout << "huh?" << std::endl; 

     } 
} 

int main(){ 
     pBase base1{ new Derived1() }; 
     pBase base2{ new Derived2() }; 

     doSomething(base1); 
     doSomething(base2); 

     //no need to call delete 
} 

私はsmart pointerC++11を使用してコードをしました。もしあなたがC++11と一緒に働いていなかったら、それをチェックしてください、あなたはそれを愛するでしょう。

なぜこのコードはHoltのコードより専門性が低いのですか?

タイプとキャストの代わりに、具体的にタイプを識別するために作られた単純なint値に依存するためです。

は、しかし、私の意見では、主な質問はかなり異なっている:

両方のコードの欠点は何ですか?

両方のコードでは、クライアントはすべての派生クラスを認識している必要があります。ホルトの答えでは、これはif statementsの部分です。私の答えでは、これはswitch statementです。

新しい派生クラスDerived3があるとどうなりますか?クライアントコードを変更する必要があります。

多形性でこれを行うことができ、キャストなしでBaseクラスでのみ動作すると思います。数字、人(学生、教授)、車(スポーツ、ピックアップ、SUVの、トラック) -

はこのような例がたくさんあります。

#include <iostream> 
#include <memory> 

struct Shape { 
     virtual float area() const = 0; 

     virtual const char *name() const = 0; 

     virtual ~Shape(){} 
}; 

struct Square : Shape { 
     Square(float a) : a(a){ 

     } 

     const char *name() const override{ 
       return "Quadrat"; // this has nothing to do with class name 
     } 

     float area() const override{ 
       return a * a; 
     } 

private: 
     float a; 
}; 

struct Circle : Shape { 
     Circle(float r) : r(r){ 

     } 

     const char *name() const override{ 
       return "Circle"; 
     } 

     float area() const override{ 
       return PI * r * r; 
     } 

private: 
     constexpr static float PI = 3.14159; 

     float r; 
}; 

using pShape = std::unique_ptr<Shape>; 

void doSomething(pShape &base){ 
     std::cout  << base->name() 
        << " has area of " 
        << base->area() 
        << "m2" 
        << std::endl; 
} 

int main(){ 
     pShape base1{ new Square(5) }; 
     pShape base2{ new Circle(5) }; 

     doSomething(base1); 
     doSomething(base2); 

     //no need to call delete 
} 

ベースShapeクラスはinterfaceと呼ばれJavaC#またはPHP、など多くのOOP言語で:これは私のお気に入りであることを起こります。どのようにメソッドを定義するかに注目してください。実装の詳細は含まれていません。

これにより、派生クラスを異なるコンパイル単位で実装することができ、クライアントコードはどのクラスで動作するか分かりません。あなたの例の場合も同様である

(typeid(*baseComp) == typeid(ComponentB)) 

2

あなたは次のようにRTTIと変数の型についての情報を得ることができます。

+0

これは、次にdynamic_castを使用しますか?また、すべての回答と同じように素晴らしい、私は(私は遅く)夜には、より多くの人々に投稿するチャンスを与えるためにそれらを受け入れるつもりだ、そして私は受け入れるだろう^ _^ –

関連する問題