2

我正在使用 C++ (vc2008) 读/写一个结构,其类型在运行时显然会根据 ID 标志发生变化。创建正确的类型和/或读取和写入将需要切换。最接近的现有示例是使用模板而不是开关,但这不允许在运行时指定类型。为了避免在多个地方创建相同的开关,我一直在研究使用递归模板来解决这个问题。这是我第一次使用这些,因此可以对代码示例进行一些重大改进!

下面是一个工作示例。正如您将在 '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

这个解决方案有许多缺点,这使得它在我看来不是最优的。其中大部分归结为 TypeList 成为主要的编译瓶颈,就像 switch case 一样。根据我的经验,这个例子中的 doWrite / doDelete 最好通过虚拟调度来解决,但实际的对象创建需要将运行时数据映射到具体类型。imo,最好的解决方案就是去工厂。如果你有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 回答