Type Match Runtime Value

I have a list of types that can be sent over the network, take this example:

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

Now I have a list of classes that correspond to each of the types, let each of them be declared as class E_T1 {...} , class E_T2 {...} , etc.

They are not derived from a common base class , and this cannot be done. Each of the classes has a verification method that I need to call when transmitting data over the network. The client sends data D and an identifier corresponding to the type of message. I need to get an object that matches the type. If necessary, I can use C ++ 0x functions.

I tried using specialized templates for types , while retaining a typedef for the object associated with it. Obviously, this was a stupid idea, since the template parameters should be a compile-time constant, so doing something on getType<data.id()>::type not possible.

Then I tried to use Boost.Variant to get a generic return type like this (used mpl vector to iterate over registered types at runtime for debbuging):

 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) 

The problem is that it is limited to 20 entries per default option. This can be rewritten, but from what I understand, the overhead of one type should be taken into account, and we are talking about several thousand types here.

Also tried boost.any, but it does not contain any type information, so the question is again. Does anyone have any good ideas on how to solve this elegantly? Looking for something where I don’t need to write a 1k switch statement anytime I process the type.

All types are displayed in compilation format, the same applies to their respective identifiers. Id -> A type decision must be executed at run time.

Thanks in advance, Robin.

+4
source share
3 answers

External polymorphism (*)

This is a well-known idiom, but it is widely used: I first encountered it in the implementation of shared_ptr , and it was very useful in my toolbar.

The idea is to create a base class for all of these types. But not having them directly from him.

 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>()); } 

So, this is a classic strategy for adding a new level of indirection.

Now you obviously do need a switch to go from value to class. Or maybe ... a map?

 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)(); } 

And now you can process them polymorphically:

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

Of course, you can make a strategy to suit your needs. For example, moving the map from select so that you can dynamically register your types (for example, for plugins).

(*) At least the name that I have for him, I will be pleased to know that it has already been named.

+5
source

I assume that you have a general way of processing a message, for example, an overloaded function:

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

Now you do not need to get the type of the object. All you need is a way to handle this type of message, given an uncoded message.

So, I recommend you fill out the factory function map:

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

Then, once you have decrypted the type, you can use handlers[type](bytes, size) to decode and process the message.

+2
source

Try variable templates and your already defined getType class:

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

And confirm the operation:

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

Using:

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

Source: https://habr.com/ru/post/1436957/


All Articles