1

Duplicate: Is there a way to instantiate objects from a string holding their class name?


Is there a (better) way in C++ to map string id to a class name. I suspect there might be a way via templates but I wasn't able to figure out the correct way.

For example if I have multiple messages, each storing in the first byte a character id. So message A12345 would instantiate class A() message B12345 would map to class B().

I have a group of message parsers where each class can parse give message. The issue is that I have to manually generate mapping between class that parses given message (e.g. class A()) and the message id (e.g. the 'A').

This is what I'm doing in the code now, and I'm thinking if there's a nice way to eliminate the switch in c++ (templates magic?):

Msg * msg;
switch(data[0])
{
    case 'A': msg = new A(); break;
    case 'B': msg = new B(); break;
    ...
}

msg->parse(data);

On the side note, most of the time the id's are actually 2 character long so 'A1' 'B1' and so on. And I'm using the class names the same to keep track of the classes, e.g. 'A1' can be parsed by class A1() and so on.

NOTE: This seems like duplicate of C++: Is there a way to instantiate objects from a string holdig their class name? so I suggest to close this question.

Community
  • 1
  • 1
stefanB
  • 77,323
  • 27
  • 116
  • 141
  • Not really, if the A and B classes share a common parent class then you could abstract some of this out into a Factory method that contains the switch statement to avoid cluttering up your code. There is some possibility for a rather complex solution to this problem but then you've added a bunch of complexity for nothing. – BobbyShaftoe May 12 '09 at 23:41

5 Answers5

2

I suggest using Boost.Any. You can then just interpret your any as an A or a B:

if (myAny.type() == typeid(A))
{
    boost::any_cast<A>(myAny).whatever();
}
else if (myAny.type() == typeid(B))
{
    // do whatever
}
rlbond
  • 65,341
  • 56
  • 178
  • 228
1

There is no way to do this using templates or anything; C++ doesn't have reflection. RTTI may help, but you can't create an instance of a class from its name at runtime (like you could with .NET or Java reflection).

However, you could make this simpler with a macro. Assuming you don't want just single character class names (as in your example), you can't use a switch. Try something like this:

#define CREATE_MSG(t) if(strcmp(data,#t)==0) msg=new t()

// I know CREATE_MSG is not a good name,
//but I couldn't think of something better

Msg *msg;

CREATE_MSG(A);
CREATE_MSG(B);
CREATE_MSG(C);

msg->parse(data);
Zifre
  • 26,504
  • 11
  • 85
  • 105
1

As someone else said, it can't be done using templates (templates are computed at compile time. But your character id is compute at runtime).

You can use a map from id to constructor function. It boils down to this question: Instantiate objects from a String holding their class name

I recommend you to keep it simple. If a plain switch will do it, keep it that way. If you later really need to have it extensible, you can still introduce some automatic look-up of character ids and so on.

Community
  • 1
  • 1
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
1

You cannot eliminate the switch (it will be if-else if they are strings). A shortcut would be to use a macro:

#define str(x) #x
#define xstr(x) str(x)
#define cond(var, type) (var == xstr(type)) { new type; }  

and use it as:

if cond(data, A)
else if cond(data, B)
dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • Why the extra str and xstr macros? Couldn't just use #type? Am I missing something? Also, you can't really compare C strings with ==. You would need a strcmp. – Zifre May 12 '09 at 23:52
  • 1) That's how macros work in both C and C++. (IIRC, This particular one i.e. 'xstr' can be found in the C standard.) 2) I am assuming that you'd go with std::string. Should've put a quote around strings. – dirkgently May 12 '09 at 23:58
  • @Zifre: you need str in cases where one of the arguments to cond is another macro that should be expanded. With it, that macro gets expanded before it's stringified. Without it, it doesn't. For instance, try precompiling "#define MYNAME A1 \n cond(data, MYNAME)" with cond as dirkgently has it, and then again with cond containing #type as you suggest. That said, macros only need to call str. Actual code calls xstr to stringify. See https://www.securecoding.cert.org/confluence/display/seccode/PRE05-C.+Understand+macro+replacement+when+concatenating+tokens+or+performing+stringification – Steve Jessop May 13 '09 at 01:25
  • "That said, macros only need to call str" - I should say, when calling it on a macro argument you only need str. If a macro is stringifying something which it wants to be expanded first should it be a macro name, then it needs xstr. – Steve Jessop May 13 '09 at 13:09
0

You can have a data structure (e.g. a dictionary or e.g. an array of pairs) which maps the string ID to a static function which constructs the class. Something like:

Msg* createA() { return new A(); }
Msg* createB() { return new B(); }

typedef Msg* (*Pfn_create)();

typedef std::map<char,Pfn_create> MsgMap;
MsgMap s_msgMap;

void initialize()
{
  s_msgMap.insert(MsgMap::pair('A',createA));
  s_msgMap.insert(MsgMap::pair('B',createB));
}

void dispatch(const Data& data)
{
  MsgMap::iterator it = s_msgMap.find(data[0]);
  Pfn_create creator = it->second;
  Msg* msg = creator();
  msg->parse(data);
}
ChrisW
  • 54,973
  • 13
  • 116
  • 224