C ++ Abstraction of command line action using interface

I am creating an application whose use would look something like this:

application --command --option1=? --option2=2? 

In principle, there can be any number of options, but only one command per application instance. Similar to how git works.

Now I thought that I would write it in C ++ in order to get some momentum and experience with STL, and go with some of those design patterns that I continue to read about. So, I implemented this:

 class Action { public: void AddParameter(std::string key, boost::any p); virtual unsigned int ExecuteAction(); protected: std::map<std::string, boost::any> parameters; }; 

I’ll explain my logic anyway, just to test it out is an abstract action. All actions need to add a parameter, so the parameter map is for us to implement at this level, but we expect ExecuteAction be implemented by derived classes, such as my simple DisplayHelpAction example, which does pretty much what it says about tin.

So now I wrote a factory, for example:

 class DetermineAction { public: DetermineAction(); vx::modero::Action getAction(std::string ActionString); private: std::map<std::string, vx::modero::Action> cmdmap; }; 

The logic is that the constructor will create a map of possible strings that you can request, and getAction will do what it says - give it a command line, and it will give you a class derived from Action that implements the desired functionality.

I am having problems with this constructor. I try this:

 this->cmdmap = std::map<std::string, Action>(); this->cmdmap.insert(pair<string, Action>("help", DisplayHelpAction())); this->cmdmap.insert(pair<string, Action>("license", DisplayLicenseAction())); 

This causes a lot of errors. Now I'm used to the Java Way interface, so you use:

 Interface I = new ConcreteClass(); 

and Java like it. So the idea I'm trying to achieve here is because what I want to do to implement getAction is this:

 return this->cmdmap[ActionString]; 

To whom should I return the class obtained from Action, after which I can start adding parameters and invoking execution.

So, to summarize, I have two questions that are closely related:

  • Soundboard. I deliberately abstract things, so there is some additional complexity, but in principle, does my approach sound? Is there an insanely obvious shortcut that I missed? Is there a better method I should use?
  • How do I configure a solution for class mapping so that I can return the correct class? A specific complaint is link time and:

     Linking CXX executable myapp CMakeFiles/myapp.dir/abstractcmd.cpp.o: In function `nf::Action::Action()': abstractcmd.cpp:(.text._ZN2vx6modero6ActionC2Ev[_ZN2vx6modero6ActionC5Ev]+0x13): undefined reference to `vtable for nf::Action' 

Just because it might be relevant, I use boost::program_options to parse the command line.


Edit 1 : Ok, now I replaced Action with Action* according to Eugene's answer and am trying to add new SomethingThatSubclassesAction to the map. I am still getting vtable error.

0
source share
1 answer
  • One thing that needs to be said right off the bat is that polymorphism at run time works in C ++ using pointers to the base class not by value. Thus, your std::map<std::string, Action> should be std::map<std::string, Action*> or your derived actions (i.e. DisplayHelpAction ) will be sliced when copied to map . Saving Action* also means that you will need to explicitly release map values ​​when you are done. Note : you can use boost :: ptr_map ( boost::ptr_map<std::string,Action> ) (as @Fred Nurk noted) or boost :: shared_ptr ( std::map<std::string,boost::shared_ptr<Action> > ) so as not to worry about explicitly releasing the highlighted Action* .
    Same thing in "Action getAction (std :: string ActionString); it should become Action* getAction(std::string ActionString);

  • The linker error (most likely) is caused by the fact that the implementation for virtual unsigned int ExecuteAction(); not implemented virtual unsigned int ExecuteAction(); . I would also say that it makes sense to make it pure virtual ( virtual unsigned int ExecuteAction() = 0; ) - in this case you do not need to provide an implementation for it. It will also ensure that Java interface semantics are closed for the Action class.

  • If you don’t have a good reason for not deriving Action objects from just boost:program_options , I would pass it on and allow each of them to access it directly rather than constructing std::map<std::string, boost::any> .

  • I would rename DetermineAction to something like ActionManager or ActionHandler .

+3
source

Source: https://habr.com/ru/post/1339832/


All Articles