3

I'm working on a personal project and I need to have a dynamic factory. Currently I have the following "system" :

class Action { ...} // Which is abstract
class A : public Action { ... }
class B : public Action { ... }
class C : public Action { ... }

...

std::map<std::string, Action *> _actions;
_action.insert( { "A", new A() } );
_action.insert( { "B", new B() } );
_action.insert( { "C", new C() } );

Action * act = _actions.find("B")->second; // Get action's pointer

But I'm not satisfied by this solution because, even if I never use these actions, they are instantiated at boot and stay in memory during all the application's life-cycle and if I want to have a "clean" object, I need to cast dynamically my object to determine its type to call the constructor by copy... I'm not really fan of this solution because I must "hard code" all cases...

I don't know if the solution that I imagine is really feasible but here is my idea :

I conserve the previous class hierarchy, which is :

class Action { ...} // Which is abstract
class A : public Action { ... }
class B : public Action { ... }
class C : public Action { ... }

But I want to use the map differently :

std::map<std::string, POINTER_ON_CLASS_CONSTRUCTOR> _actions;
_actions.insert( { "A", &A::A } );
_actions.insert( { "B", &B::B } );
_actions.insert( { "C", &C::C } );

Action * act = new (_actions.find("B")->second);

To resume my idea :

I want to map a key with a pointer on each constructor I want to map and then, instantiate dynamically an action-based object according to the key.

Thanks per advance for your help and if you have a suggestion to solve this issue, do not hesitate to submite it :)


Context :

I'm creating a CLI for my software and if I want to code this solution is to map the first word of an action with a associated action.

Example : If I enter "add file.txt", "add" will be the key of my map to retrieve a "clean" instance on the class "AddAction" (which is a child of Action - which is abstract and common to all action-based class).

Edit : before posting this question, I've searched on the web and on this forum but I haven't found what I was looking for.. :/

Brian61354270
  • 8,690
  • 4
  • 21
  • 43
arnaudm
  • 157
  • 1
  • 10
  • I removed that STL tag since you do not appear to be using the SGI STL. See [What's the difference between “STL” and “C++ Standard Library”?](https://stackoverflow.com/questions/5205491/whats-the-difference-between-stl-and-c-standard-library) – Brian61354270 Apr 22 '21 at 18:54
  • 1
    Add a static factory method to each class that just calls `new` and store pointers to those. – Chris Dodd Apr 22 '21 at 19:02
  • The address of a constructor may not be taken - [constructors have no names](https://en.cppreference.com/w/cpp/language/constructor). – Ted Lyngmo Apr 22 '21 at 19:03

2 Answers2

7

You could map the names to factory functions, then call those functions each time you want to create a new instance. Wrapping it in a unique_ptr will give you the destruction you want after use. live link

std::unordered_map<std::string, std::function<std::unique_ptr<Action>()>> ACTIONS = {
  {"A", [] { return std::make_unique<A>(); }},
  {"B", [] { return std::make_unique<B>(); }},
  {"C", [] { return std::make_unique<C>(); }}
};

int main() {
  auto action = ACTIONS["A"]();
}
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
2

You can implement the clone idiom to avoid having to dynamic_cast objects to determine which copy constructor to call, eg:

class Action {
public:
    virtual ~Action() {};
    virtual Action* clone() = 0;
    ...
};

class A : public Action {
public:
    Action* clone() { return new A(*this); }
    ...
};

class B : public Action {
public:
    Action* clone() { return new B(*this); }
    ...
};

class C : public Action {
public:
    Action* clone() { return new C(*this); }
    ...
};

...

std::map<std::string, Action*> _actions;
_action.insert( { "A", new A } );
_action.insert( { "B", new B } );
_action.insert( { "C", new C } );

Action *act = _actions["B"]->clone(); // see Note below...
...
delete act;

Note: There is no reason to use map::find() when you are not validating the returned iterator. It would be easier to just use map::operator[] instead, or map::at() if you want to throw an error if the specified key is not found.

However, if you do not want to store actual objects in the map, you can use factory functions instead (you can't obtain a pointer to a constructor), eg:

class Action { ... }
class A : public Action { ... }
class B : public Action { ... }
class C : public Action { ... }

...

using ActionFactoryFunc = Action* (*)();

std::map<std::string, ActionFactoryFunc> _actions;
_action.emplace( "A", []() -> Action* { return new A; } );
_action.emplace( "B", []() -> Action* { return new B; } );
_action.emplace( "C", []() -> Action* { return new C; } );

Action *act = _actions["B"]();
...
delete act;

Lastly, you really should be using std::unique_ptr<Action> instead of raw Action* pointers, eg:

#include <memory>

class Action {
public:
    virtual ~Action() = default;
    virtual std::unique_ptr<Action> clone() = 0;
    ...
};

class A : public Action {
public:
    std::unique_ptr<Action> clone() override { return std::make_unique<A>(*this); }
    ...
};

class B : public Action {
public:
    std::unique_ptr<Action> clone() override { return std::make_unique<B>(*this); }
    ...
};

class C : public Action {
public:
    std::unique_ptr<Action> clone() override { return std::make_unique<C>(*this); }
    ...
};

...

std::map<std::string, std::unique_ptr<Action>> _actions;
_action.emplace( "A", std::make_unique<A>() );
_action.emplace( "B", std::make_unique<B>() );
_action.emplace( "C", std::make_unique<C>() );

auto act = _actions["B"]->clone();
...

Or:

#include <memory>

class Action { ... }
class A : public Action { ... }
class B : public Action { ... }
class C : public Action { ... }

...

using ActionFactoryFunc = std::unique_ptr<Action> (*)();

std::map<std::string, ActionFactoryFunc> _actions;
_action.emplace( "A", []() -> std::unique_ptr<Action> { return std::make_unique<A>(); } );
_action.emplace( "B", []() -> std::unique_ptr<Action> { return std::make_unique<B>(); } );
_action.emplace( "C", []() -> std::unique_ptr<Action> { return std::make_unique<C>(); } );

auto act = _actions["B"]();
...
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770