在C++中,是否有一种将元数据与函数关联起来的整洁方法

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

本文关键字:关联 函数 元数据 起来 方法 一种 是否 C++      更新时间:2023-10-16

我有一个包含许多命令行选项的代码库。目前,如果在命令行上传递命令,每个命令行选项都与要运行的函数指针一起存在于一个表中。

例如

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

我的问题是,桌子很大,而功能却在其他地方。我更希望命令的字符串位于每个函数旁边。

例如:

COMMAND_ARG("command1")
void Func1()
{
    dostuff
    ...
}
COMMAND_ARG("command2")
void Func2()
{
    dostuff
    ...
}
COMMAND_ARG("command3")
void Func3()
{
    dostuff
    ...
}

这可能吗?

您可以使用由函数地址专门化的模板来实现这一点:

#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("%sn", FnMeta<some>::meta);
    printf("%sn", FnMeta<another>::meta);
}

上面的想法是FnMeta<>::meta没有被定义。然而,不同的翻译单元(.cc文件)可以提供FnMeta<>::meta的专门化的定义。这样,当使用FnMeta<X>::meta时,链接器在另一个翻译单元中找到它的适当定义。

对于这个特殊的问题有不同的方法。您可以使用继承,通过继承创建一个基本Command,然后实现一些execute函数(您也可以实现helpvalidate….)。然后创建一个调度器函数,将名称与命令的实际实现相关联(在排序的查找表中,可能是map)。

虽然这并不能解决locality的问题,但该问题可能是真实的,也可能不是真实的。也就是说,命令的实现可能无处不在,但只有一个地方可以确定CLI中可用的命令。

如果locality对您来说非常重要(代价是在源代码中没有一个位置列出所有使用中的命令),您可以提供一个全局可访问的注册机制,然后提供一个助手类型,在构建过程中将函数注册到该机制中。然后,您可以为每个函数定义创建一个这样的对象。

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);

我个人更喜欢走另一条路。。。在代码中有一个列出所有命令的位置,因为它允许在一个位置找到执行命令的代码的命令(文本)。也就是说,当你有几个命令,而其他人需要维护其中一个命令时,它可以更容易地从命令行转到执行命令的实际代码。

我同意Maxim Yegorushkin的回答,即最好尝试使用静态机制,但这里有几种运行时方法可以满足将行为和函数名称保持在一起的要求。

方法#1,命令对象:

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
};

方法#2,带选择器的功能:

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
};

所以你想使用预处理器宏,是吗?有些接缝不好,但我经常用。这个答案将基于命令注册表:

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;
};

有一些静态注册表应该在头以外的地方:

Command::RegistryType Command::registry;

让我们看看我们需要什么(稍微简单一点):

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

因此,我们需要创建一些从Command继承并实现了_execute方法的类的对象。由于方法可以在类外定义,因此该宏将包含所有需要的代码,并使用brake:中的代码

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

所以这是宏的完美位置:

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

我希望你喜欢。