30

私は C++ RTTI に精通しており、その概念が興味深いと感じています。

それでも、正しく使用するよりも悪用する方法がたくさんあります (RTTI スイッチの恐怖が頭に浮かびます)。開発者として、私はそれの実行可能な用途を 2 つだけ見つけました (そして使用しました) (より正確には、1.5 です)。

サンプルコード/疑似コードを含めて、RTTI が問題に対する実行可能なソリューションである方法をいくつか共有していただけますか?

注: 目的は、ジュニア開発者が相談し、批判し、学ぶことができる実行可能な例のリポジトリを用意することです。

編集: C++ RTTI を使用して以下のコードを見つけることができます

// A has a virtual destructor (i.e. is polymorphic)
// B has a virtual destructor (i.e. is polymorphic)
// B does (or does not ... pick your poison) inherits from A

void doSomething(A * a)
{
   // typeid()::name() returns the "name" of the object (not portable)
   std::cout << "a is [" << typeid(*a).name() << "]"<< std::endl ;

   // the dynamic_cast of a pointer to another will return NULL is
   // the conversion is not possible
   if(B * b = dynamic_cast<B *>(a))
   {
      std::cout << "a is b" << std::endl ;
   }
   else
   {
      std::cout << "a is NOT b" << std::endl ;
   }
}
4

10 に答える 10

8

boost :: anyオブジェクトはどうですか?

これは基本的にRTTI情報を使用して任意のオブジェクトを保存し、そのオブジェクトを取得するにはboost ::any_cast<>を使用します。

于 2008-10-26T21:11:07.933 に答える
8

Acyclic Visitor (pdf) は、それをうまく利用しています。

于 2008-10-26T19:40:41.987 に答える
6

RTTI を dynamic_cast と共に使用して、派生クラスへのポインターを取得し、それを使用して高速で型に特化したアルゴリズムを呼び出すことができます。また、基本クラスを介して仮想メソッドを使用する代わりに、直接インライン呼び出しを行います。

これにより、GCC を使用して速度が大幅に向上しました。Visual Studio も同様に機能していないようで、dynamic_cast ルックアップが遅くなる可能性があります。

例:

D* obj = dynamic_cast<D*>(base);
if (obj) {
    for(unsigned i=0; i<1000; ++i)
        f(obj->D::key(i));
    }
} else {
    for(unsigned i=0; i<1000; ++i)
        f(base->key(i));
    }
}
于 2008-10-26T21:47:33.213 に答える
5

実生活で in の使用法を見つけたとは言えませんが、RTTI はC++のマルチメソッドに対する可能な解決策として、Effective C++で言及されています。これは、メソッドのディスパッチが動的な型のthisパラメーターに対して行われるが、静的な型の引数に対して行われるためです。

class base
{
  void foo(base *b) = 0; // dynamic on the parameter type as well
};

class B : public base {...}
class B1 : public B {...}
class B2 : public B {...}

class A : public base
{
  void foo(base *b)
  {
    if (B1 *b1=dynamic_cast<B1*>(b))
      doFoo(b1);
    else if (B2 *b2=dynamic_cast<B2*>(b))
      doFoo(b2);
  }
};
于 2008-10-26T19:45:41.087 に答える
3

私はかつて航空機のシミュレーションに取り組んだことがありますが、それは彼らが (やや紛らわしい) 「シミュレーション データベース」と呼んだものを持っていました。float や int や string などの変数を登録すると、人々はそれらを名前で検索し、それらへの参照を引き出すことができます。モデル (「SimModel」から派生したクラスのオブジェクト) を登録することもできます。私が RTTI を使用した方法は、特定のインターフェースを実装するモデルを検索できるようにすることでした。

SimModel* SimDatabase::FindModel<type*>(char* name="")
{
   foreach(SimModel* mo in ModelList)
   if(name == "" || mo->name eq name)
   {
       if(dynamic_cast<type*>mo != NULL)
       {
           return dynamic_cast<type*>mo;
       }
   }
   return NULL;
}

SimModel 基本クラス:

class public SimModel
{
    public:
        void RunModel()=0;
};

インターフェースの例は「EngineModel」です。

class EngineModelInterface : public SimModel
{
    public:
        float RPM()=0;
        float FuelFlow()=0;
        void SetThrottle(float setting)=0; 
};

次に、Lycoming および Continental エンジンを作成します。

class LycomingIO540 : public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rpm;
        }
        float FuelFlow()
        {
            return throttleSetting * 10.0;
        }
        void SetThrottle(float setting) 
        {
            throttleSetting = setting
        }
        void RunModel() // from SimModel base class
        {
            if(throttleSetting > 0.5)
                rpm += 1;
            else
                rpm -= 1;
        }
    private:
        float rpm, throttleSetting;
};
class Continental350: public EngineModelInterface 
{
    public:
        float RPM()
        {
            return rand();
        }
        float FuelFlow()
        {
            return rand;
        }
        void SetThrottle(float setting) 
        {
        }
        void RunModel() // from SimModel base class
        {
        }
};

さて、ここに誰かがエンジンを必要とするコードがあります:

.
.
EngineModelInterface * eng = simDB.FindModel<EngineModelInterface *>();
.
.
fuel = fuel - deltaTime * eng->FuelFlow();    
.
.
.

コードはかなり疑似的ですが、アイデアが伝わることを願っています。1 人の開発者がエンジンに依存するコードを作成できますが、エンジン インターフェイスを実装するものがある限り、それが何であるかは気にしません。そのため、タンク内の燃料の量を更新するコードは、FindModel<>() 関数と、彼が使用したいと考えている純粋な仮想 EngineModel インターフェイスを除くすべてから完全に切り離されています。1 年後に誰かが新しいエンジン モデルを作成し、それを SimulationDatabase に登録すると、燃料を更新した上記の担当者が自動的に使用を開始します。実際には、実行時に新しいモデルをプラグイン (DLL) としてロードできるように作成し、SimulationDatabase に登録すると、FindModel<>() で見つけることができます。それらを探していたコードは、新しい DLL が存在する数か月前にコンパイルされ、DLL に組み込まれていました。また、SimModel から派生した新しいインターフェイスを追加することもできます。それらを 1 つの DLL で実装するものと、別の DLL でそれらを検索するものを使用して、両方の DLL をロードすると、モデルを取得するために FindModel<>() を実行できます。もう一方。メインアプリが構築されたとき、インターフェイス自体は存在しませんでしたが。

括弧書きとして、RTTI は常に DLL の境界を越えて機能するとは限りません。とにかくQtを使っていたので、qobject_cast代わりにdynamic_cast. すべてのクラスは QObject から継承する (そして moc を取得する) 必要がありましたが、qobject メタデータは常に利用可能でした。DLL を気にしない場合、または RTTI が DLL の境界を越えて機能するツールチェーン(ハッシュなどではなく文字列比較に基づく型比較) を使用している場合は、上記のすべてがdynamic_cast問題なく機能します。

于 2008-10-26T19:50:26.960 に答える
3

XML ファイルにシリアル化するクラス ツリーで使用します。デシリアライゼーションでは、パーサー クラスは、サブクラスの型の列挙を持つ基本クラスへのポインターを返します (解析するまでどの型かわからないため)。オブジェクトを使用するコードがサブクラス固有の要素を参照する必要がある場合、enum 値と dynamic_cast をサブクラス (パーサーによって作成されたもの) に切り替えます。このようにして、コードは、パーサーにエラーがなく、列挙値と返されたクラス インスタンス タイプの間に不一致がないことを確認できます。取得する必要があるサブクラス固有のデータがある可能性があるため、仮想関数も十分ではありません。

これは、RTTI が役立つ可能性がある場所の一例にすぎません。問題を解決する最も洗練された方法ではないかもしれませんが、RTTI を使用すると、このパターンを使用する場合にアプリケーションがより堅牢になります。

于 2008-10-27T02:22:58.837 に答える
2

場合によってstatic_castは、C スタイルのキャストだけでは十分ではなく、 が必要になります。この例は、恐ろしいひし形の階層dynamic_castがある場合です(Wikipedia の画像)。

ダイヤモンド継承

struct top {
};

struct left : top { 
    int i;
    left() : i(42) {}
};

struct right : top {
    std::string name;
    right() : name("plonk") { }
};

struct bottom : left, right {
};

bottom b;
left* p = &b;

//right* r = static_cast<right*>(p); // Compilation error!
//right* r = (right*)p;              // Gives bad pointer silently 
right* r = dynamic_cast<right*>(p);  // OK
于 2008-10-27T14:51:46.250 に答える
1

私のプロジェクトでのユースケース (特定のケースに対するより良い解決策を知っている場合は、コメントしてください):

  1. 1800 INFORMATION と同じことはすでに述べています。

    派生クラスdynamic_castoperator==またはの実装が必要になります。operator<または、少なくとも私は他の方法を知りません。

  2. のようなものboost::anyまたは他のバリアント コンテナーを実装する場合。

  3. (可能性のあるインスタンスはと) (最大で 1 つ) を持つClientクラスの 1 つのゲームでは、 function が必要でした。この関数はめったに使用されないため、追加のローカル メンバー変数とこれを処理するすべての追加コードで煩雑になるのを避けたいと考えました。std::set<Player*>NetPlayerLocalPlayerLocalPlayerLocalPlayer* Client::localPlayer()Client

  4. いくつかの実装を持つVariable抽象クラスがあります。登録されているすべてVariableの はいくつかの にありstd::set<Variable*> varsます。BuiltinVarまた、 に保存されるタイプの組み込み変数がいくつかありstd::vector<BuiltinVar> builtinsます。場合によっては、 and があり、内部Variable*にあるかどうかを確認する必要があります。これは、いくつかのメモリ範囲チェックまたは経由で行うことができます (いずれにせよ、 のすべてのインスタンスがこのベクター内にあることを確認できます)。BuiltinVar*builtinsdynamic_castBuiltinVar

  5. のグリッド コレクションがあり、1 つのグリッド内にオブジェクト (特殊な)GameObjectがあるかどうかを確認する必要があります。を除いて常に false を返す関数を使用するか、RTTI を使用することができます。このような関数を頻繁に実装する例は他にもたくさんあり、そのために基本クラスが非常に雑然としています。PlayerGameObjectbool GameObject::isPlayer()PlayerObject::isOfTypeXY()

    これは、 のような他の非常に特殊な関数の場合もありObject::checkScore_doThisActionOnlyIfIAmAPlayer()ます。基本クラスでそのような関数を持つことが実際に意味がある場合とそうでない場合を判断するには、ある程度の常識が必要です。

  6. アサーションやランタイム セキュリティ チェックに使用することもあります。

  7. 一部のデータのポインタを一部の C ライブラリ (たとえば、SDL など) の一部のデータ フィールドに格納する必要があり、後で別の場所またはコールバックとして取得します。dynamic_cast期待どおりの結果が得られることを確認するために、ここで a を実行します。

  8. TaskManagers のキューを実行するクラスがありますTask。一部の をリストに追加するときに、同じタイプの他の をキューからTask削除したいと考えています。Task

于 2010-07-18T22:45:46.793 に答える
0

数年前、Qt でキャンバスベースの作業を行うときに RTTI を使用しました。オブジェクトのヒット テストを行う場合、RTTI を使用して「ヒット」する形状で何をするかを決定するのは非常に便利でした。しかし、それ以外の方法で製品コードで使用したことはありません。

于 2008-10-26T20:35:48.427 に答える
0

Dynamic Double Dispatch と Templatesで使用しています。基本的に、オブジェクトの興味深い部分だけを観察/聞くことができます。

于 2008-10-27T07:47:16.217 に答える