Class lookup structure array in C++

2019-02-20 08:51发布

I'm trying to create a structure array which links input strings to classes as follows:

struct {string command; CommandPath cPath;} cPathLookup[] = {
    {"set an alarm", AlarmCommandPath},
    {"send an email", EmailCommandPath},
    {"", NULL}
};

which will be used as follows:

CommandPath *cPath = NULL;
string input;
getline(cin, input);
for(int i = 0; cPathLookup[i] != ""; i++) {
        if(cPathLookup[i].command == input)
                cPath = new cPathLookup[i].cPath;
}

Obviously, this code is meaningless, but I think my intention is apparent - depending on input, I'd like cPath to be initialized as either a new AlarmCommandPath or a new EmailCommandPath. I could handle it with a function returning an instance depending on input, but a whole sequence of ifs just seems inelegant.

I should also note that, in case it's not apparent and important, that AlarmCommandPath and EmailCommandPath are derived from CommandPath, and CommandPath is an abstract class.

Thanks for any help you can offer.

EDIT: I just noticed that, in spite of CommandPath being abstract, I have a declaration:

CommandPath *cPath = NULL;

in working code. Why does that compile?

4条回答
ら.Afraid
2楼-- · 2019-02-20 09:10

AlarmCommandPath and EmailCommandPath are derived from COmmandPath, correct?

In this case you cannot assign an instance of AlarmCommandPath/EmailCommandPath to CommandPath - it is technically possible, but it won't do what you want. The instance CommandPath will remain an instance of CommandPath (it will have virtual function table of CommandPath), no matter what you assign to it.

You need to use factory methods (a function that will return CommandPath*). Something like that:

struct A{
};

struct B: public A{
};

struct C: public A{
};

A* factoryA(){
    return new A();
}

A* factoryB(){
    return new B();
}

A* factoryC(){
    return new C();
}

typedef A* (*FactoryMethod)();

struct{
    const char* command;
    FactoryMethod factoryMethod;
} factoryTable[] = {
    {"A", factoryA},
    {"B", factoryB},
    {"C", factoryC},
    {0,0}
};
查看更多
3楼-- · 2019-02-20 09:19

I assume you are trying to implement a table lookup as a replacement to a large if\else statement in your system.

For clarity I would go with a Factory design pattern instead. Having large if/else logic is only really bad if it is repeated around your code in many places. As long as it is in one place i.e. a Factory then in my opinion, you have a good design.

查看更多
手持菜刀,她持情操
4楼-- · 2019-02-20 09:32

You can't store a type in a struct, but you can store a pointer to a function that creates a type.

CommandPath * CreateEmail() {
     return new EmailCommandPath;
}

CommandPath * CreateAlarm() {
     return new AlarmCommandPath;
}

Then your struct looks like:

typedef Command * (* CreateFunc)();
struct MyMap {
   string command;
   CreateFunc func;
};

and the map:

MyMap m[] = {{"email", CreateEmail }, {"alarm", CreateAlarm}};

You then look up as before to get some index i, and use it:

CommandPath * p = m[i].func():

And you can create pointers to abstract types - you can't create instances of them.

查看更多
We Are One
5楼-- · 2019-02-20 09:36

Personally I don't see it as being a huge problem if you just have 1 factory for creating different "CommandPaths" for different values of the string it receives. Anyway, your code won't work because you can't store types the way you're trying.

If I had to do this, then for one I would use function pointers to factory functions and use a std::map to map strings to these, like shown in this code, and maybe wrap the pointers in an appropriate smart-pointer, instead of using raw pointers:

#include <string>
#include <map>

struct A {
};

struct B : public A {
};

struct C : public A {
};

A *BFactory(){
    return new B();
}

A *CFactory(){
    return new C();
}

typedef A *(*Factory)();
typedef std::pair<std::string,Factory> Pair;
typedef std::map<std::string,Factory> Map;

Pair Pairs[] = 
{
    std::make_pair( "alarm", BFactory ),
    std::make_pair( "email", CFactory )
};

Map Lookup( Pairs, Pairs + sizeof(Pairs)/sizeof(*Pairs) );

A *CreateInstance( const std::string &Type )
{
    Map::const_iterator i = Lookup.find( Type );

    if( i != Lookup.end() )
        return i->second();
    else
        return 0;
}

As for your question about pointers and abstract classes, you can have a pointer to an abstract class, but you can't instantiate an abstract class.

查看更多
登录 后发表回答