このタイプリストベースのクラス階層生成コードの代替案は何ですか?
-
12-10-2019 - |
質問
オブジェクトがインターフェイスを実装してオプションの機能を提供できるシンプルなオブジェクトモデルを使用しています。心臓では、オブジェクトが実装する必要があります getInterface
(一意の)インターフェイスIDが与えられたメソッド。次に、このメソッドは、オブジェクトが要求されたインターフェイスを実装しない場合に、インターフェイスまたはnullにポインターを返します。これを説明するためのコードスケッチは次のとおりです。
struct Interface { };
struct FooInterface : public Interface { enum { Id = 1 }; virtual void doFoo() = 0; };
struct BarInterface : public Interface { enum { Id = 2 }; virtual void doBar() = 0; };
struct YoyoInterface : public Interface { enum { Id = 3 }; virtual void doYoyo() = 0; };
struct Object {
virtual Interface *getInterface( int id ) { return 0; }
};
このフレームワークで作業するクライアントを容易にするために、「getinterface」実装を自動的に生成する小さなテンプレートを使用して、クライアントがインターフェイスで必要な実際の機能を実装するだけです。アイデアは、具体的なタイプを導き出すことです Object
すべてのインターフェイスと同様に、次に許可します getInterface
ポインターを返すだけです this
(適切なタイプにキャスト)。これがテンプレートとデモの使用法です。
struct NullType { };
template <class T, class U>
struct TypeList {
typedef T Head;
typedef U Tail;
};
template <class Base, class IfaceList>
class ObjectWithIface :
public ObjectWithIface<Base, typename IfaceList::Tail>,
public IfaceList::Head
{
public:
virtual Interface *getInterface( int id ) {
if ( id == IfaceList::Head::Id ) {
return static_cast<IfaceList::Head *>( this );
}
return ObjectWithIface<Base, IfaceList::Tail>::getInterface( id );
}
};
template <class Base>
class ObjectWithIface<Base, NullType> : public Base
{
public:
virtual Interface *getInterface( int id ) {
return Base::getInterface( id );
}
};
class MyObjectWithFooAndBar : public ObjectWithIface< Object, TypeList<FooInterface, TypeList<BarInterface, NullType> > >
{
public:
// We get the getInterface() implementation for free from ObjectWithIface
virtual void doFoo() { }
virtual void doBar() { }
};
これは非常にうまく機能しますが、醜い2つの問題があります。
私にとってブロッカーは、これがMSVC6で動作しないことです(これはテンプレートのサポートが不十分ですが、残念ながらサポートする必要があります)。 MSVC6は、これをコンパイルするときにC1202エラーを生成します。
クラスの全範囲(線形階層)は再帰によって生成されます
ObjectWithIface
レンプレート。これは私にとっては問題ではありませんが、残念ながら私は単一のことをすることはできませんswitch
インターフェイスIDをポインターにマッピングするステートメントgetInterface
. 。代わりに、階層内の各ステップは単一のインターフェイスをチェックし、リクエストをベースクラスに転送します。
誰かがこの状況を改善する方法を提案していますか?上記の2つの問題を修正することにより ObjectWithIface
テンプレート、またはオブジェクト/インターフェイスフレームワークを使いやすくする代替案を提案することにより。
解決
そのようなことはどうですか?
struct Interface
{
virtual ~Interface() {}
virtual std::type_info const& type() = 0;
};
template <typename T>
class InterfaceImplementer : public virtual Interface
{
std::type_info const& type() { return typeid(T); }
};
struct FooInterface : InterfaceImplementer<FooInterface>
{
virtual void foo();
};
struct BarInterface : InterfaceImplementer<BarInterface>
{
virtual void bar();
};
struct InterfaceNotFound : std::exception {};
struct Object
{
void addInterface(Interface *i)
{
// Add error handling if interface exists
interfaces.insert(&i->type(), i);
}
template <typename I>
I* queryInterface()
{
typedef std::map<std::type_info const*, Interface*>::iterator Iter;
Iter i = interfaces.find(&typeid(I));
if (i == interfaces.end())
throw InterfaceNotFound();
else return static_cast<I*>(i->second);
}
private:
std::map<std::type_info const*, Interface*> interfaces;
};
あなたは何かよりももっと手の込んだものが欲しいかもしれません type_info const*
動的ライブラリの境界を越えてこれを行いたい場合。何かのようなもの std::string
と type_info::name()
正常に動作します(少し遅いですが、この種の極端な発送には遅いものが必要になる可能性があります)。数値IDを製造することもできますが、これを維持するのが難しいかもしれません。
Type_Infosのハッシュを保存することは、別のオプションです。
template <typename T>
struct InterfaceImplementer<T>
{
std::string const& type(); // This returns a unique hash
static std::string hash(); // This memoizes a unique hash
};
そして使用します FooInterface::hash()
インターフェイスと仮想を追加するとき Interface::type()
クエリするとき。
他のヒント
dynamic_cast
この正確な問題を解決するために言語内に存在します。
使用例:
class Interface {
virtual ~Interface() {}
}; // Must have at least one virtual function
class X : public Interface {};
class Y : public Interface {};
void func(Interface* ptr) {
if (Y* yptr = dynamic_cast<Y*>(ptr)) {
// Returns a valid Y* if ptr is a Y, null otherwise
}
if (X* xptr = dynamic_cast<X*>(ptr)) {
// same for X
}
}
dynamic_cast
また、複数の仮想継承などのものをシームレスに処理します。
編集:
COMのQueryInterfaceを確認できます。コンパイラ拡張機能を備えた同様のデザインを使用しています。 COMコードが実装されているのを見たことがなく、ヘッダーのみを使用しましたが、検索できます。