2012-08-16 45 views
2

我正在使用C++(vc2008)读取/写入结构,其类型在运行时明显根据ID标志更改。创建正确的类型和/或读写需要一个开关。现有的最接近的例子是Using template instead of switch,但这不允许在运行时指定类型。为了避免在多个地方创建相同的交换机,我一直在研究使用递归模板来解决这个问题。这是我第一次使用这些代码,因此可能会对代码示例做一些重大改进!递归模板列表,而不是使用基于键/ ID的搜索的硬编码switch语句

下面是一个工作示例。正如你将在'main()'中看到的那样,所使用的类型id是一个变量int,它可以被设置为任何运行时值。在类型列表<>上调用一个函数将遍历这些类型,直到它达到一个匹配的ID或一个空类型。

#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>实例化中保存重复的代码。

编辑:该解决方案最好不需要运行时设置'添加'类型查找或喜欢。

欢呼声中,克雷格

+1

你确定这是对的吗?如果你有一个没有基础的相互依赖的类,类型列表是很好的。对于您的案例工厂(基于具有这种类型和基础的模板参数的地图)是最佳选择。 – Torsten 2012-08-16 08:05:37

+0

我不能有任何工厂对象的运行时设置,并且已知类型的数量。如果有更好的方法来实现这一点,我将非常感激,我不确定如何在不增加复杂性的情况下使用工厂,也不需要编译时间。 – Crog 2012-08-16 08:29:54

回答

1

该解决方案有一些缺点,这使得在我的脑海里就次优。其中大部分归结为类型列表成为主要的编译瓶颈,与交换机的情况一样。根据我的经验,本例中的doWrite/doDelete可以通过虚拟调度更好地解决,但实际的对象创建需要将运行时数据映射到具体类型。对此,最好的解决办法就是去工厂。

// 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; 
}; 

很简单,因为一个事实,即它是更灵活(可以办理,如果您有Loki,因为它是那么简单示例返回boost::shared_ptr<>)。

这种方法的主要优点是您可以将注册码与具体类型放在同一翻译单元中。更容易分离客户端代码和修改具体类型的方式不会导致需要工厂的所有内容的重新编译。作为奖励,绩效量表也更好。

如果你不想要虚拟调度,你可以使用相同的方法,但是使用成员函数指针并提供实例,这可以使用boost::bind几乎相同的方法来解决。

编辑:所以是的,错过了你想要它完全编译基于时间,虽然说实话我不太明白任何好处。