简体   繁体   中英

Is there a tidy way of associating metadata with functions in C++

I have a codebase with many command line options. Currently, each command line option lives in a table along with a function pointer to run if the command is passed in on the command line.

eg

static CommandFunction s_Commands[] =
{
   { "command1", Func1 },
   { "command2", Func2 },
   { "command3", Func3 },
   etc...
};

My problem with this is, the table is huge, and the functions live elsewhere. I would prefer the string for the command to live right beside each function.

So for example:

COMMAND_ARG("command1")
void Func1()
{
    dostuff
    ...
}

COMMAND_ARG("command2")
void Func2()
{
    dostuff
    ...
}

COMMAND_ARG("command3")
void Func3()
{
    dostuff
    ...
}

Is this possible?

You can do that with a template specialized by an address of a function:

#include <stdio.h>

// In a header file.
template<void(*Fn)()>
struct FnMeta
{
    static char const* const meta;
};
// no definition of meta


// some.cc
void some() {}
template<> char const* const FnMeta<some>::meta = "some";

// another.cc
void another() {}
template<> char const* const FnMeta<another>::meta = "another";

// main.cc    
int main() {
    printf("%s\n", FnMeta<some>::meta);
    printf("%s\n", FnMeta<another>::meta);
}

The idea above is that FnMeta<>::meta is not defined. However, different translation units (.cc files) can provide a definition of a specialization of FnMeta<>::meta . This way when FnMeta<X>::meta is used the linker finds the appropriate definition of it in another translation unit.

There are different approaches to this particular problem. You can use inheritance, by which you create a base Command and then implement some execute function (you can also implement help , validate ....). Then create a dispatcher function that associates the names with the actual implementations of the commands (in a lookup table of sorts, possibly a map ).

While this does not solve your issue with locality , that issue might or not be real. That is, the implementation of the commands might be all over the place, but there is a single place that determines what commands are available in the CLI.

If locality is such an important thing for you (at the cost of not having a single place in your source code where all commands in use are listed), you can provide a registration mechanism that is globally accessible, then provide a helper type that during construction will register the function into the mechanism. You can then create one such object with each function definition.

CommandRegistry& getCommandRegistry();    // Access the registry
struct CommandRegister {
   CommandRegister(const char* name, Function f) {
      getCommandRegistry().registerCmd(name,f);
   }
   // Optionally add deregistration
};
// ...
void Func2() {...}
static CommandRegister Func2Registration("function2",&Func2);

I personally prefer to go the other way... having a single place in the code where all commands are listed, as it allows for a single location in which to find the command (text) to code that executes it. That is, when you have a few commands and someone else needs to maintain one of them, it makes it easier to go from the command line to the actual code that executes it.

I agree with Maxim Yegorushkin's answer that it is best to try to use static mechanisms, but here's a couple of runtime approaches that meet the requirement of keeping the behavior and the function name together.

Approach #1, Command Object:

class AbstractCommand{
public:
    virtual ~AbstractCommand() {}
    virtual void exec() = 0;
    virtual const char *commandName() const = 0;
};

class Command1 : public AbstractCommand{
public:
    virtual void exec() { /* do stuff */ }
    virtual const char *commandName() const { return "command name 1"; }
};

class Command2 : public AbstractCommand{
public:
    virtual void exec() { /* do stuff */ }
    virtual const char *commandName() const { return "command name 2"; }
};

static AbstractCommand *s_commands[] {
    new Command1(),
    new Command2(),
    ...,
    0
};

Approach #2, function with selector:

enum CommandExecOption { GET_NAME, EXEC };

typedef void* (*command_func_t)( CommandExecOption opt );

void *Command1Func( CommandExecOption opt )
{
    switch(opt){
    case GET_NAME: return "command 1"; break;
    case EXEC:
        /* do stuff */
        break;
    }
    return 0;
}

void *Command2Func( CommandExecOption opt )
{
    switch(opt){
    case GET_NAME: return "command 2"; break;
    case EXEC:
        /* do stuff */
        break;
    }
    return 0;
}

command_func_t s_commands[] = { 
    Command1Func,
    Command2Func,
    ...,
    0
};

So you want to use preprocessor macros, huh? There are seams to be bad, but I use them frequently. This answer will be based on command registry:

class Command
{
public:
    Command(std::string const& _name):name(_name){ registry[_name]=this; }
    virtual ~Command() { registry.erase(name); }

    static void execute( std::string const& name ) {
        RegistryType::iterator i = registry.find(name);
        if(i!=registry.end()) i->second->_execute();
        //some exeption code here
    }

protected:
    virtual void _execute() = 0;

private:
    const std::string name;
    typedef std::map< std::string, Command* > RegistryType;
    static RegistryType registry;
};

There are static registry that should be somewhere else than header:

Command::RegistryType Command::registry;

Lets look what we need (changed a bit to be simpler):

COMMAND_ARG( doSomething )
{
    cout << "Something to do!" << std::endl;
}

So we need to create some object of a class that inherit from Command and have implemented _execute method. Since method can be defined outside of class this macro will enclose all needed code, and use the code in braced:

class CommanddoSomething : public Command {
public:
    CommanddoSomething () : Command( "doSomething" ) {}
private:
    virtual void _execute();
} commanddoSomething;
void CommanddoSomething :: _execute()
{
    cout << "Something to do!" << std::endl;
}

So this is perfect place for a macro:

#define COMMAND_ARG( NAME ) \
    class Command ## NAME : public Command { \
        public: Command ## NAME () : Command( #NAME ) {} \
        private: virtual void _execute(); \
    } command ## NAME; \
    void Command ## NAME :: _execute()

I hope you like it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM