classof メソッドの実装を自動化する方法が (何らかの方法でテンプレートを使用して) 存在するかどうか疑問に思っていましたか?
はい、classof メソッドを自動化する方法はあります。非常に単純なプロセスを自動化すると、はるかにスケーラブルになるため、LLVM ページが手動で作成された classof メソッドのセットを示す理由が本当にわかりません。
これは非常に基本的な解決策です:
class TypedObject {
public:
virtual ~TypedObject() { };
virtual int getClassId() const { return 0; };
static int getStaticClassId() { return 0; };
virtual bool isOfType(int aID) const { return (aID == 0); };
template <typename T>
bool isOfClass() const { return isOfType( T::getStaticClassId() ); };
};
ランタイム キャスト (つまり、dynamic_cast
) 関数は次のようになります。
template <typename T>
T* runtime_ptr_cast(TypedObject* p) {
if( (p) && (p->isOfClass<T>()) )
return static_cast<T*>( p );
return NULL;
};
template <typename T>
typename std::enable_if<
std::is_const< T >::value,
T* >::type runtime_ptr_cast(const TypedObject* p) {
if( (p) && (p->isOfClass<T>()) )
return static_cast<T*>( p );
return NULL;
};
必要なのは、仮想関数と静的関数の作成を自動化するマクロだけです。
#define MY_RTTI_SYSTEM_CREATE_TYPE_1_BASE( NEWCLASSID, BASECLASSNAME ) \
public: \
virtual int getClassId() const { return NEWCLASSID; }; \
static int getStaticClassId() { return NEWCLASSID; }; \
\
virtual bool isOfType(int aID) const { \
return ((aID == NEWCLASSID) || BASECLASSNAME::isOfType(aID)); \
};
次に、次のような新しいクラスを作成できます。
class Foo : public TypedObject {
// ... some code, as usual ...
// call the macro with a given ID number and the name of the base-class:
MY_RTTI_SYSTEM_CREATE_TYPE_1_BASE(1, TypedObject)
};
これは次のことにつながります。
int main() {
Foo f;
TypedObject* b = &f;
// check the type:
if( b->isOfClass<Foo>() )
std::cout << "b is indeed for class Foo!" << std::endl;
// make a dynamic cast:
Foo* pf = runtime_ptr_cast<Foo>( b );
if( pf )
std::cout << "cast to 'Foo*' was successful!" << std::endl;
const TypedObject* cb = b;
const Foo* cpf = runtime_ptr_cast<const Foo>( cb );
if( cpf )
std::cout << "cast to 'const Foo*' was successful!" << std::endl;
Foo* pf2 = runtime_ptr_cast<Foo>( cb ); // ERROR: no such function (invalid cast).
};
そしてもちろん、型を登録するためのマクロをさらに作成するだけで、これを多重継承に拡張することもできます。このスキームには無数のバリエーションもあります (個人的には、私の実装では、型をグローバル リポジトリに登録し、ファクトリ関数へのアクセスも許可します)。
作成する各クラスで MACRO 呼び出しを使用する必要を回避する実用的な方法はないと思います。私はしばらくそれについて考えてきました (しばらく前に、私は自分自身を作っていたので)、最も簡単でクリーンな解決策は、クラスで MACRO 呼び出しを行うことであると結論付けました (私はマクロのマクロを非常に軽蔑していますが、全般的)。しかし、私にはわかりません。おそらく、他の人は、混乱を引き起こしすぎたり、邪魔になりすぎたりしない、これに対するより優れた (テンプレートベースの) ソリューションを持っているかもしれません。私はこのスキームを何年も使用してきましたが、とてもきれいできれいです。
多重継承はありませんが、いくつかのレベルの継承があります。
上記のスキームは、どのレベルの継承でも機能します (つまり、スケーラブルなソリューションです)。いつの日かそうしたい場合は、多重継承に簡単に適応させることもできます。
メモリフットプリントへの影響は可能な限り最小限にする必要があります
LLVM は、仮想関数を使用せず、代わりに基本クラスで整数 ID データ メンバーを使用するソリューションを好むことを知っています。そのようなスキームで上記と同じ種類の機能を実現するのは少し難しくなります (ただし可能です)。1 つのポインター (vtable ポインター) のスペースのみを占有する仮想関数を使用すると、はるかに簡単になります。また、クラスがすでにポリモーフィックである場合、コストはまったくかかりません。もちろん、上記は組み込みの C++ RTTI よりもはるかに軽量です。したがって、整数 ID (または列挙型) ソリューションで確保できる数バイトを実際に圧縮したい場合を除き、上記で示したような仮想関数に基づくソリューションを使用することをお勧めします。
動的割り当てを実行することはできません。
通常、動的割り当ては必要ありません。より複雑な (そして機能が豊富な) RTTI 実装のみが、何らかの動的割り当てを必要とします。「classof()」(したがって、動的キャスト) を実行できるようにすることだけが必要な場合は、動的メモリ割り当ては必要ありません。