1

Not sure I can do this. I have question that is a variant of [this other question](Can you create a std::map of inherited classes? ).

Basically the answer to the question below is to create a map of pointers to inherited classes, where the map is searchable based on the first map entry. Then you call use a pointer to address any virtual function you want.

I have implemented a command processor application with a few commands. The map is:

std::map<string, BaseCmdClass*> cmd_map;

All good so far. However we are updating our code to provide potentially hundreds of derived classes. I don't want to create them all in a factory class at the start of the code, too much memory use.

I want to construct an inherited class dynamically when I need it, then delete using the base class pointer. I want to avoid a big case statement to call each constructor.

Is there a way I can create a std::map or use another STL container that can lookup what constructor to call? Then when I need the derived constructor I can lookup this map to find the correct derived constructor to call.

Community
  • 1
  • 1
  • Your Qn is not stupid, but to me it's not clear. Also tag "c++" to get larger audience. – iammilind Apr 27 '17 at 13:38
  • Instead of a map of class instances, create a map of factory functions. Roughly: `typedef BaseClass* (*Factory)(); std::map factory_map;` – Igor Tandetnik Apr 27 '17 at 14:02
  • Cheers thaks for replying. Yes Factory is fine, but you still will have a big long list of factory constructor calls. I think. – Paul McManus Apr 27 '17 at 14:17
  • Of course. There needs to be *some* data structure that relates a string to a corresponding class, one way or another. – Igor Tandetnik Apr 27 '17 at 14:33

3 Answers3

0
struct BaseCmdClass {
  virtual bool do_something() = 0;
  virtual ~BaseCmdClass() {}
};
using upCmd = std::unique_ptr<BaseCmdClass>;
using CmdFactory = std::function<upCmd()>;

using Command = std::string;

std::unordered_map<Command, CmdFactory>& command_factories() {
  static std::unordered_map<Command, CmdFactory> retval;
  return retval;
}
upCmd make_cmd( Command const& cmd ) {
  auto mit = command_factories().find(cmd);
  if (mit == command_factories().end() || !*mit) return {};
  return (*mit)();
}


// in CopyCmd.cpp
struct CopyCmd : BaseCmdClass
  bool do_something() override final {
    std::cout << "did copy";
  };
  static auto registered = command_factories().insert(
    "copy",
    []{ return std::make_unique<CopyCmd>(); }
  );
};

now anyone can make_cmd("copy") and get an instance of CopyCmd via unique pointer to its BaseCmdClass.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Is there a way I can create a std::map or use another STL container that can lookup what constructor to call?

Sure. You cannot store pointers to the constructors directly, but you can store pointers to factory functions that call the constructors.

using CmdFactory = BaseCmdClass*();
std::map<std::string, CmdFactory*> factory_map;

You can use a helper template for generating the factory:

template<class T>
BaseCmdClass* factory() {
    return new T;
}

You can add a factory like this:

factory_map["Derived1"] = factory<Derived1>;

I want to construct an inherited class dynamically when I need it, then delete using the base class pointer. I want to avoid a big case statement to call each constructor.

Deleting using the base class pointer is just fine, as long as the destructor is virtual. No need for a case statement.


Further development:

You can use std::function instead of a function pointer if you have need for stateful factories.

I recommend returning a std::unique_ptr<BaseCmdClass> from the factories to avoid memory leaks.

For lower memory overhead, you can use an array instead of a map if you use an integer as the key, but the code that constructs the array will have to know about all of the children.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

One of the solutions is to have parallel hierarchy for creators:

class BaseCmdClass {
  ...
};

class BaseCmdCreator;
class BaseCmdFactory {
public:
    static BaseCmdFactory &instance();
    void registerCreator( const std::string &name, BaseCmdCreator *cr );
    ...
}; 

class BaseCmdCreator {
public:
    BaseCmdCreator( const std::string &name )
    {
         BaseCmdFactory::instance().registerCreator( name, this );
    }
    virtual std::unique_ptr<BaseCmdClass> create() = 0;
};

template<class T>
class CmdCreator : public BaseCmdCreator {
public:
    CmdCreator( const std::string &name ) : BaseCmdCreator( name ) {}
    virtual std::unique_ptr<BaseCmdClass> create() override
    {
        return std::make_unique<T>();
    }
};

Now how to use it - in .cpp file where you define particular CmdClass you create a static object:

// Class FooCmdClass defined here

namespace {
    CmdCreator<FooCmdClass> registerFoo( "FooCmd" );
}

now if you link object file created from this .cpp your class FooCmdClass will be registered. You can do this even through plugin mechanism - fully dynamically. So adding new command will not require any change in base class, only adding new .cpp file to the project.

Slava
  • 43,454
  • 1
  • 47
  • 90