2

実行時に明らかにIDフラグに基づいて型が変化するC++(vc2008)を使用して構造体を読み書きしています。正しいタイプの作成および/または読み取りと書き込みには、切り替えが必要になります。最も近い既存の例は、スイッチの代わりにテンプレートを使用することですが、これでは実行時にタイプを指定できません。複数の場所で同じスイッチを作成しないようにするために、この問題を解決するために再帰テンプレートの使用を調査してきました。これらを使用するのはこれが初めてなので、コード例にいくつかの大きな改善を加えることができるかもしれません!

以下は実際の例です。'main()'でわかるように、使用される型IDは、任意のランタイム値に設定できる変数intです。TypeList <>で関数を呼び出すと、一致するIDまたはvoid型に到達するまで、型を繰り返し処理します。

#include <stdio.h>
#include <iostream>

//Base type
struct Base
{
    //NOTE: The virtual destructor can be added  to aid with debugging
    //virtual ~Base(){}

    friend std::ostream& operator << ( std::ostream& stream, const Base& rhs )
    { return stream << "Base";  }
};

struct A : Base
{
    friend std::ostream& operator << ( std::ostream& stream, const A& rhs )
    { return stream << "A";  }
};

struct B : Base
{
    friend std::ostream& operator << ( std::ostream& stream, const B& rhs )
    { return stream << "B";  }
};

struct C : Base
{
    friend std::ostream& operator << ( std::ostream& stream, const C& rhs )
    { return stream << "C";  }
};

//Recursive template type
// - If the ID/key does not match the next type is checked and so on
template < unsigned int kID, typename _Type, typename _TNext >
struct TypeList
{
    typedef _Type Type;
    typedef typename _TNext::Base Base;

    static Base* doNew( unsigned int id )
    { return id == kID ? new _Type() : (Base*)_TNext::doNew(id); }

    static void doDelete(unsigned int id, Base* rhs )
    { id == kID ? delete (_Type*)rhs : _TNext::doDelete(id, rhs ); }

    static std::ostream& doWrite( unsigned int id, std::ostream& stream, const Base* rhs ) 
    { return id == kID ? stream << (*(const _Type*)rhs) : _TNext::doWrite(id, stream, rhs); }
};

//Specialise the 'void' case to terminate the list
// TODO; this doesn't seem as elegant as possible!? How can we separate the logic from the functionality better...
template < unsigned int kID, typename _Type >
struct TypeList<kID, _Type, void>
{
    typedef _Type Type;
    typedef _Type Base;

    static _Type* doNew( unsigned int id )
    { return id == kID ? new _Type() :0; }

    static void doDelete(unsigned int id, _Type* rhs )
    { if ( id == kID ) delete rhs; }

    static std::ostream& doWrite( unsigned int id, std::ostream& stream, const _Type* rhs ) 
    { return id == kID ? stream << (*(const _Type*)rhs) : stream; }
};

// ID values used to identify the different structure types
enum eID
{
    ID_A,
    ID_B,
    ID_C,
};

//Create our ID and Type list
typedef TypeList<   ID_A,   A,
    TypeList<       ID_B,   B,
    TypeList<       ID_C,   C, 
    TypeList<       -1 ,    Base,       void> > > > TypesList;

int _tmain(int argc, _TCHAR* argv[])
{
    eID type = ID_C; //, We are dealing with a type of 'C'  
    Base* newInst = TypesList::doNew( type );   //Create a new C
    TypesList::doWrite( type, std::cout, newInst ); //Write 'C' to the console  
    TypesList::doDelete( type, newInst );   //Delete C
    return 0;
}

これとこれを行うための他の/より良い方法についての人々の見解は何ですか?主に、ロジックをクラスの機能からうまく分離して、TypeList <,,_Type>およびTypeList<,,void>のインスタンス化で重複したコードを保存する方法があります。

編集:ソリューションは、ルックアップなどにタイプを「追加」するための実行時のセットアップを必要としないことが好ましい。

乾杯、クレイグ

4

1 に答える 1

1

このソリューションには多くの欠点があり、私の考えでは最適ではありません。そのほとんどは、switch の場合と同様に、TypeList がコンパイルの主要なボトルネックになるという事実に帰着します。私の経験では、この例の doWrite / doDelete は仮想ディスパッチによって解決されたほうがよいですが、実際のオブジェクトの作成では、実行時データを具象型にマッピングする必要があります。これに対する最善の解決策は、工場に行くことです。Lokiをお持ちの場合は、次のように簡単です。

// BaseFactory.h
typedef Loki::SingletonHolder< Loki::Factory< Base, std::string > > BaseFactory;
#define REGISTER_BASE_FACTORY( x ) \
static bool BOOST_PP_CAT( registerBaseFac, x ) = BaseFactory::Instance().Register( BOOST_PP_STRINGIZE( x ), boost::phoenix::new_< x >() );

// For example A.cpp
REGISTER_BASE_FACTORY( x );

// Somewhere else
...
Base* someInstance = BaseFactory::Instance().CreateObject("A");
assert( typeid( *someInstance ) == typeid( A ) );
...

個人的には、次のような別の工場ベースを使用しています。

#pragma once
#include "boost/unordered_map.hpp"
#include <cassert>

template< typename KeyType, typename ProductCreatorType >
class Factory
{
    typedef boost::unordered_map< KeyType, ProductCreatorType > CreatorMap;
public:
    const ProductCreatorType& operator()( const KeyType& a_Key ) const
    { 
        typename CreatorMap::const_iterator itrFnd = m_Creators.find( a_Key ); 
        assert( itrFnd != m_Creators.end() );
        return itrFnd->second;
    } 
    ProductCreatorType& operator()( const KeyType& a_Key )
    { 
        typename CreatorMap::iterator itrFnd = m_Creators.find( a_Key ); 
        assert( itrFnd != m_Creators.end() );
        return itrFnd->second;
    } 
    bool RegisterCreator( const KeyType& a_Key, const ProductCreatorType& a_Creator )
    {
        return m_Creators.insert( std::make_pair( a_Key, a_Creator ) ).second;
    }
private:
    CreatorMap m_Creators;
};

単純に、より柔軟であるという事実のためです(たとえば、返すなどを処理できboost::shared_ptr<>ます)。

このアプローチの主な利点は、具象型と同じ翻訳単位に登録コードを含めることができることです。クライアントとライブラリのコードを分離する方がはるかに簡単で、具体的な型を変更しても、ファクトリを必要とするすべてのものが再コンパイルされることはありません。おまけとして、パフォーマンススケールも優れています。

仮想ディスパッチが必要ない場合は、同じアプローチを使用できますが、代わりにメンバー関数ポインターを使用してインスタンスを提供します。これは、を使用してほぼ同じアプローチで解決できますboost::bind

編集:そうです、完全にコンパイル時間ベースにしたかったのを逃しましたが、正直なところ、メリットはまったくありません。

于 2012-08-16T08:59:34.180 に答える