10 October 2012

Storing a type on the example of a simple messenger


Hi there, I have been thinking a long time about storing a type in C++. So now I want to share with you my insights. As we all know type is something that defines the amount of necessary space and the behavior for the objects of that type. So what if we need to store a type to reuse it later? This is the main problem considered in this article.
Unlike some other languages, C++ does not introduce types as objects, so they cannot directly be stored like the latter. Though, we may think of an indirect way. For example, we can indirectly get the type of an object. To achieve this, we need just one simple type, and a function, which are presented below:

template <typename T>
struct TypeRepresentation
{
       typedef T type;
};

template <typename T>
TypeRepresentation<T> get_type(const T&)
{
       return TypeRepresentation<T>();
}

What is bad here is though we get an appropriate object, we cannot use the member type ‘type’, because we only have an object, but not the type of it:

int a = 5;
get_type(a).type // error

The member types (introduced by using typedef keyword) are only accessible through the containing type name, not an object. So to access ‘type’ we need its qualified name:

TypeRepresentation<int>::type

But to use the name we use the containing type, which we assume we do not know. In this case if we use it, we know that ‘type’ is int so this kind of use is senseless. Imagine that we somehow can use the type having only the object of TypeRepresentation<> type. We then could store this objects in a container, say std::vector<boost::any>. But as soon as we wanted to use the objects, i.e. the member ‘type’, we should convert the object to the original type with any_cast, and … we do not know the original type, otherwise we again would not need the ‘type’ member at all.

The basic idea is: however we try to wrap up the type, we will still come back to it somehow, and as it is not an object, we cannot store it as an object. There are still some cases when we need to know about the types, and we need to store that information somehow. To make it clearer, let us write a simple messenger class. We will provide a registration mechanism to register for messages from concrete types of objects, and also functions to send messages. The most interesting part here is the registration for messages from specified type of sender. So we need to somehow store the type which the registered object wants to listen to.

Before C++11 the only type describing the type was type_info from <typeinfo> header. It can be easily retrieved from any object using operator typeid. Nevertheless, it only was meant for informative purpose, and we cannot construct it ourselves or copy it to store somewhere. So to know whether some object is of wanted type we can compare the results of typeid operator called both for the object and the wanted type, for example,

if (typeid(obj) == typeid(Sender))
{
       // Got it, now do what you wanted to do
}

But still we need to store the type_info object to register the receiver for further consideration, which is impossible. Fortunately, C++11 introduced a new type, type_index, which is actually a wrapper around the type_info, but is both CopyConstructible and CopyAssignable. So now we have a chance to store it. So using std::type_index, we can handle any type-specific registration. The code for the Messenger class follows:

class Messenger
{
private:
       // Prohibit explicit construction and copying
       // My compiler does not support 'delete' and 'default' keywords
       Messenger() {}
       Messenger(const Messenger&);
       Messenger& operator=(const Messenger&);

       std::map<std::type_index, std::list<IReceiver *>> typeToReceivers;
       std::list<IReceiver *> allReceivers;

public:
       static Messenger& Get()
       {
              static Messenger messenger;

              return messenger;
       }

       template <class Sender>
       void RegisterForMessagesFromType(IReceiver *receiver)
       {
              std::map<std::type_index, std::list<IReceiver *>>::
              iterator seeker = this->typeToReceivers.find(
                                std::type_index(typeid(Sender)));
              if (seeker != this->typeToReceivers.end())
              {
                     seeker->second.push_back(receiver);
              }
              else
              {
                     std::list<IReceiver *> newList;
                     newList.push_back(receiver);
                     typeToReceivers.insert(std::make_pair(
                         std::type_index(typeid(Sender)), newList));
              }
       }

       void RegisterForMessages(IReceiver *receiver)
       {
              std::list<IReceiver *>::const_iterator
              seeker = std::find(this->allReceivers.begin(),
                              this->allReceivers.end(), receiver);
              if (seeker == this->allReceivers.end())
              {
                     this->allReceivers.push_back(receiver);
              }
       }

       template <class Sender>
       void UnregisterForMessagesFromType(IReceiver *receiver)
       {
              std::map<std::type_index, std::list<IReceiver *>>::
              iterator seeker = this->typeToReceivers.find(
                                std::type_index(typeid(Sender)));
              if (seeker == this->typeToReceivers.end())
              {
                     return;
              }
              std::list<IReceiver *>::iterator iter =
                  std::find(seeker->second.begin(),
                            seeker->second.end(), receiver);
              if (iter != seeker->second.end())
              {
                     seeker->second.erase(iter);
              }
       }

       template <class Sender>
       void SendMessageFrom(const IMessage& msg, const Sender& from)
       {
              std::map<std::type_index, std::list<IReceiver *>>::
              const_iterator seeker = this->typeToReceivers.find(
                                   std::type_index(typeid(from)));
              if (seeker != this->typeToReceivers.end())
              {
                     std::for_each(seeker->second.begin(),
                                   seeker->second.end(),
                                   [&msg] (IReceiver *rcvr)
                                   { rcvr->Receive(msg); });
              }
       }

       template <class Receiver>
       void SendMessagesTo(const IMessage& msg)
       {
              std::for_each(allReceivers.begin(), allReceivers.end(),
                            [&msg] (IReceiver *rcvr)
                            { if (typeid(*rcvr) == typeid(Receiver))
                                           { rcvr->Receive(msg); } });
       }
};

This class works fine believing my tests. I missed only the IMessage and IReceiver interfaces, here is the code for them:

class IMessage
{
public:
       virtual std::string Get() const = 0;
       virtual void Set(const std::string&) = 0;
};

class IReceiver
{
public:
       virtual void Receive(const IMessage&) = 0;
};

Actually we could register a member function of a predefined signature of the receiver class, not to require the receiver to be derived from IReceiver, but that would complicate the code. You can customize the class, add new members to support filtering by message types, not the senders, or sending to receivers of specified types.

There is one more newly introduced keyword in C++, namely decltype, which retrieves the type of the expression or object, but it has really nothing to do with storing the types, as it would require storing the object to know the type of, and this in its turn implies some limitations on the object’s type.

I hope someone has learnt something new within this article. If you have any idea of how to really store the type, I would be happy to know about it. Thanks for your time.

No comments:

Post a Comment