在C++中,是否有一种将元数据与函数关联起来的整洁方法
Is there a tidy way of associating metadata with functions in C++
我有一个包含许多命令行选项的代码库。目前,如果在命令行上传递命令,每个命令行选项都与要运行的函数指针一起存在于一个表中。
例如
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
函数(您也可以实现help
、validate
….)。然后创建一个调度器函数,将名称与命令的实际实现相关联(在排序的查找表中,可能是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()
我希望你喜欢。
- "error: no matching function for call to"构造函数错误
- 什么时候调用组成单元对象的析构函数
- 关联容器的下界复杂性:成员函数与非成员函数
- 如何创建一个对象创建函数,该函数将由与其关联的名称调用?
- 构造函数是否unique_ptr初始化原始指针unique_ptr析构函数是否也删除关联的原始指针?
- 将整数(文字)与函数相关联,让呼叫者查询拖鞋的数量
- 如何在C++中创建将函数与参数相关联的 Map?
- 关联容器,比较函数不是元素类型的一部分吗?
- 函数类型定义可以与外部"C"相关联吗?
- 从函数返回变量地址时如何修复"与局部变量关联的堆栈内存地址"?
- MISRA C++规则 14-5-1:在与类型关联的命名空间中声明的泛型函数模板的名称
- 将 POSIX::open 函数关联到命名空间
- 在C 17中,为什么关联容器具有``擦除''成员函数,该函数会采用(non-`const`)``iTerator'
- std::async 和 lambda 函数在C++中没有给出关联的状态
- 将问题与导出的函数相关联
- 如何在给定函数之外获取与 C 闭包关联的(向上)值
- 如何将更改后的行与C代码的git存储库中的函数关联起来
- 在C++中,是否有一种将元数据与函数关联起来的整洁方法
- 为什么没有更简单的查找函数用于关联 std 容器
- 将文本字符串与函数关联