6

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

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 向量在运行时迭代注册的类型以进行调试):

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 -> 类型解析需要在运行时进行。

在此先感谢,罗宾。

4

3 回答 3

9

外部多态性(*)

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

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

于 2012-09-29T14:32:43.783 回答
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)来解码和处理消息。

于 2012-09-29T14:36:51.863 回答
2

尝试可变参数模板和您已经定义的 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);
于 2012-09-29T14:52:01.903 回答