运行时值到类型的映射

Runtime value to type mapping

本文关键字:映射 类型 运行时      更新时间:2023-10-16

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

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。获得如下常见可返回类型的变体(使用mpl向量在运行时迭代已注册的类型以进行解bug):

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 switch语句的东西。

所有类型在编译类型中都是已知的,它们对应的ID也是如此。Id->类型解析需要在运行时进行。

提前感谢,罗宾。

外部多态性(*)

这是一个广为人知的习语,但它被广泛使用:我第一次在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中移出,这样您就可以动态注册您的类型(就像插件一样)。

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

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

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)来解码和处理消息。

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

enum types { T1_ID, T2_ID, .... };
class T1; class T2; class T3; ....
template <types t> struct getType;
template <> struct getType<T1_ID> { typedef T1 type; };  
template <> 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);