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