运行时值到类型映射

时间:2012-09-29 14:10:06

标签: c++ networking

我有一个可以通过网络发送的类型列表,请举例:

enum types {
    E_T1,
    E_T2,
    E_T3,
    E_T4
};

现在我有一个与每个类型对应的类列表,假设每个类都声明为class E_T1 {...}class E_T2 {...}等。

它们不是来自公共基类,并且不可能这样做。每个类都有一个验证方法,我需要通过网络发送数据来调用。客户端发送数据D,并将id对应于消息类型。我需要掌握与该类型对应的对象。如果需要,我可以使用C ++ 0x功能。

到目前为止,我尝试使用types的专用模板,为与之相关的对象保存一个typedef。这显然是一个愚蠢的想法,因为模板参数需要编译时间常数,因此无法在getType<data.id()>::type上执行某些操作。

然后我尝试使用Boost.Variant来获得这样一个常见的可返回类型(使用mpl vector迭代在runntime上注册的类型进行重新布局):

template <typename C>
struct getType() {
    typedef C type;
}

typedef boost::mpl::vector<
    getType<E_T1>,
    getType<E_T2>,
    getType<E_TX>...
> _types;

typedef boost::make_variant_over<_types>::type _type;

//use a map to store each type <-> id
boost::unorderd_map<types, _type> m;
m[E_T1] = getType<E_T1>();

m[data.id()]::type x; //<- access type, can now call x.validate(data)

这个问题是每个默认情况下每个变体限制为20个条目。这可以被覆盖,但从我所理解的应该考虑每种类型的开销,我们在这里谈论几千种类型。

还尝试过boost.any,但它不包含任何类型信息,因此再次出现问题。有谁有任何好的想法如何才能优雅地解决这个问题? 寻找一些我无需在处理类型时编写1k开关语句的东西。

所有类型都以编译类型命名,相应的ID也是如此。 Id - &gt;类型解析需要在运行时发生。

提前致谢, 罗宾。

3 个答案:

答案 0 :(得分:6)

外部多态性(*)

这是一个广为人知的习语,但它被广泛使用:我第一次在shared_ptr实现中遇到它,它在我的工具箱中非常有用。

这个想法是为所有这些类型实际创建一个基类。但不要让他们直接从中获取。

class Holder {
public:
    virtual ~Holder() {}

    virtual void verify(unsigned char const* bytes, size_t size) const = 0;

}; // class Holder

template <typename T>
class HolderT: public Holder {
public:
     HolderT(): _t() {}

     virtual void verify(unsigned char const* bytes, size_t size) const {
         _t.verify();
     }

private:
    T _t;
}; // class HolderT

template <typename T>
std::unique_ptr<Holder> make_holder() {
    return std::unique_ptr<Holder>(new HolderT<T>());
}

因此,这是添加新级别间接的经典策略。

现在,您显然执行需要切换以从值移动到类。或许......一张地图?

using maker = std::unique_ptr<Holder> (&)();
using maker_map = std::unordered_map<types, maker>;

std::unique_ptr<Holder> select(types const E) {
    static maker_map mm;
    if (mm.empty()) {
        mm.insert(std::make_pair(E_T1, make_holder<EC_T1>));
        // ...
    }

    maker_map::const_iterator it = mm.find(E);

    if (it == mm.end()) { return std::unique_ptr<Holder>(); }

    return (*it->second)();
 }

现在你可以多态地处理它们了:

void verify(types const E, unsigned char const* bytes, size_t size) {
    std::unique_ptr<Holder> holder = select(E);
    if (not holder) { std::cerr << "Unknown type " << (int)E << "\n"; return; }

    holder->verify(bytes, size);
}

当然,欢迎您根据自己的需要制定相应的策略。例如,将地图移出select,以便您可以动态注册类型(例如插件)。

(*)至少这是我的名字,我很高兴发现它已被命名。

答案 1 :(得分:2)

我假设您有一种处理消息的通用方法,例如重载函数:

void handle_message(const E_T1& msg);
void handle_message(const E_T2& msg);
//...

现在,您并不需要获取对象的类型。给定未解码的消息,您只需要一种处理该类型消息的方法。

因此,我建议您填充工厂功能的地图:

std::unordered_map<types, std::function<void (unsigned char const* bytes, size_t size)> handlers;
handlers[E_E1] = [](unsigned char const* bytes, size_t size) { handle_message(E_T1(bytes, size)); };
// ...

然后,一旦解码了类型,就可以使用handlers[type](bytes, size)来解码和处理消息。

答案 2 :(得分:2)

尝试可变参数模板和您已经定义的getType类:

enum types { T1_ID, T2_ID, .... };
class T1; class T2; class T3; ....

template <types t> struct getType;
template <types t> struct getType<T1_ID> { typedef T1 type; };  
template <types t> struct getType<T2_ID> { typedef T2 type; };
...  

操作验证:

template <types...>
struct type_operation;

template <types t1, types... rest> 
struct type_operation<t1, rest...>
{
   void verify(types t)
   {
      if (t == t1) 
      { 
         typename getType<t1>::type a; 
         a.verify(); // read from network and verify the rest of data....
      }
      else type_operation<rest...>::verify(t, data);
   }
};

template <> 
struct type_operation<>
{
   void verify(types t)
   {
      ostringstream log; log << "not suppoted: " << t;
      throw std::runtime_error(log.str()); // 
   }
};

用法:

typedef type_operation<T1_ID, T2_ID, T3_ID, ,,.., TN_ID> type_mapping;
types id;
readFromNetwork(id);
type_mapping::verify(id);