この種のことを説明しているほとんどすべてのC ++リソースは、RTTI(ランタイム型識別)を使用するよりもポリモーフィックなアプローチを好むべきだと私に教えています。一般に、私はこの種のアドバイスを真剣に受け止め、その根拠を理解しようとします-結局のところ、C ++は強力な獣であり、その完全な深さを理解することは困難です。ただし、この特定の質問については、空白を描いています。インターネットがどのようなアドバイスを提供できるかを確認したいと思います。まず、RTTIが「有害であると見なされている」理由として引用されている一般的な理由を挙げて、これまでに学んだことを要約しましょう。
一部のコンパイラはそれを使用しません/ RTTIは常に有効ではありません
私は本当にこの議論を買いません。これは、C ++ 14の機能を使用するべきではないと言っているようなものです。C++ 14の機能をサポートしていないコンパイラが世に出ているからです。それでも、C ++ 14機能の使用を思いとどまらせる人はいません。プロジェクトの大部分は、使用しているコンパイラーとその構成方法に影響を与えます。gccのマンページを引用しても:
-fno-rtti
C ++ランタイム型識別機能(dynamic_castおよびtypeid)で使用する仮想関数を持つすべてのクラスに関する情報の生成を無効にします。言語のこれらの部分を使用しない場合は、このフラグを使用してスペースを節約できます。例外処理は同じ情報を使用しますが、G ++は必要に応じてそれを生成することに注意してください。dynamic_cast演算子は、実行時の型情報を必要としないキャスト、つまり「void *」または明確な基本クラスへのキャストに引き続き使用できます。
これは、RTTIを使用していない場合は無効にできることを示しています。つまり、Boostを使用していない場合は、Boostにリンクする必要はありません。誰かがでコンパイルする場合を想定する必要はありません-fno-rtti
。さらに、この場合、コンパイラは大音量でクリアします。
追加のメモリが必要/遅くなる可能性がある
RTTIを使いたくなったときはいつでも、ある種の型情報やクラスの特性にアクセスする必要があります。RTTIを使用しないソリューションを実装する場合、これは通常、この情報を格納するためにいくつかのフィールドをクラスに追加する必要があることを意味します。そのため、memory引数は一種の無効です(この例は後で説明します)。
実際、dynamic_castは遅くなる可能性があります。ただし、スピードが重要な状況での使用を回避する方法は通常いくつかあります。そして、私は代替案をまったく見ていません。このSOの回答は、基本クラスで定義された列挙型を使用して型を格納することを提案しています。これは、すべての派生クラスを事前に知っている場合にのみ機能します。それはかなり大きな「もし」だ!
その回答から、RTTIのコストも明確ではないようです。異なる人々は異なるものを測定します。
エレガントなポリモーフィックデザインはRTTIを不要にします
これは私が真剣に受け止めている一種のアドバイスです。この場合、自分のRTTIユースケースをカバーする優れた非RTTIソリューションを思い付くことはできません。例を挙げましょう。
ある種のオブジェクトのグラフを処理するライブラリを書いているとしましょう。ライブラリを使用するときにユーザーが独自の型を生成できるようにしたい(そのため、enumメソッドは使用できません)。私のノードの基本クラスがあります:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
現在、私のノードはさまざまなタイプにすることができます。これらはどうですか:
class red_node : virtual public node_base
{
public:
red_node();
virtual ~red_node();
void get_redness();
};
class yellow_node : virtual public node_base
{
public:
yellow_node();
virtual ~yellow_node();
void set_yellowness(int);
};
地獄、なぜこれらの1つでもない:
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
最後の機能は面白いです。これを書く方法は次のとおりです。
void orange_node::poke_adjacent_oranges()
{
auto adj_nodes = get_adjacent_nodes();
foreach(auto node, adj_nodes) {
// In this case, typeid() and static_cast might be faster
std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
if (o_node) {
o_node->poke();
}
}
}
これはすべて明確でクリーンなようです。属性やメソッドを必要としない場所で定義する必要はありません。基本ノードクラスは無駄のない状態を保つことができます。RTTIがない場合、どこから始めればよいですか?多分私はnode_type属性を基本クラスに追加できます:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
private:
std::string my_type;
};
std :: stringは型にとって良いアイデアですか?そうではないかもしれませんが、他に何を使用できますか?数字を作り、誰もまだそれを使用していないことを願っていますか?また、私のorange_nodeの場合、red_nodeとyellow_nodeのメソッドを使用したい場合はどうなりますか?ノードごとに複数のタイプを保存する必要がありますか?複雑そうです。
結論
この例は、過度に複雑でも異常でもないように見えます(ノードがソフトウェアによって制御される実際のハードウェアを表し、ノードが何であるかに応じて非常に異なることを行う、私の日常業務で同様の作業をしています)。しかし、私はテンプレートや他の方法でこれを行うためのきれいな方法を知りません。私が問題を理解しようとしているのであって、私の例を擁護しているのではないことに注意してください。上記でリンクしたSO回答やWikibooksのこのページなどの私のページを読んで、RTTIを誤用しているように思われますが、その理由を知りたいと思います。
それで、私の元の質問に戻ります:RTTIを使用するよりも「純粋なポリモーフィズム」が好ましいのはなぜですか?
node_base
ライブラリの一部であり、ユーザーが独自のノードタイプを作成することを示唆しているという考えに注意していないことに注意してください。それから彼らはnode_base
別のソリューションを許可するように変更することはできないため、おそらくRTTIが最良のオプションになるでしょう。一方、RTTIを使用せずに新しいノードタイプがよりエレガントに収まるように、このようなライブラリを設計する方法は他にもあります(他の方法で新しいノードタイプを設計することもできます)。