现在我可以在任何地方通过“名称”来调用它们,而无需预先声明每一个。
要在编译时调用任何函数,您需要前向声明它。因为您想在编译时调用它们,所以不需要使用字符串文字。而且您只能使用预处理器而不是模板来执行此操作,因为您不能为模板指定标识符名称(至少在 C++03 中)。
例子:
#include <iostream>
#define CALL_FUNC(func, args) name_ ##func args;
void name_func1(){
std::cout << "func1" << std::endl;
}
void name_func2(int a){
std::cout << "func2:" << a << std::endl;
}
int main(int argc, char** argv){
CALL_FUNC(func1, ());
CALL_FUNC(func2, (46));
return 0;
}
您可以在函数体内前向声明函数:
#include <iostream>
int main(int argc, char** argv){
void name_func(int);
name_func(42);
return 0;
}
void name_func(int arg){
std::cout << "func1:" << arg << std::endl;
}
因此,从技术上讲,您甚至不需要为此使用预处理器。
您无法避免前向声明,除非所有函数参数及其类型都已知,在这种情况下,您可以使用宏隐藏前向声明。
#include <iostream>
#define FUNC_NAME(func) name_ ##func
#define CALL_VOID_FUNC(func) { void FUNC_NAME(func)(); FUNC_NAME(func)(); }
int main(int argc, char** argv){
CALL_VOID_FUNC(func1);//not forward declared
return 0;
}
void name_func1(){
std::cout << "func1" << std::endl;
}
或者,如果您想在每次调用函数并知道参数数量时指定函数参数类型:
#include <iostream>
#define FUNC_NAME(func) name_ ##func
#define CALL_FUNC_1ARG(func, type1, arg1) { void FUNC_NAME(func)(type1); FUNC_NAME(func)(arg1); }
int main(int argc, char** argv){
CALL_FUNC_1ARG(func1, int, 42);
return 0;
}
void name_func1(int arg){
std::cout << "func1:" << arg << std::endl;
}
或者,如果您的函数可以采用可变数量的参数。(解析可变参数很有趣):
#include <iostream>
#define FUNC_NAME(func) name_ ##func
#define CALL_FUNC_VARIADIC(func, args) { void FUNC_NAME(func)(...); FUNC_NAME(func)args; }
int main(int argc, char** argv){
CALL_FUNC_VARIADIC(func1, (42, 43, 44));
return 0;
}
void name_func1(...){
//std::cout << "func1:" << arg << std::endl;
}
如果你想使用字符串(如“func1”),那么你试图在运行时定位函数,而不是在编译时,即使你真的不这么认为。那是因为"funcname"
与 ( ) 没有什么不同std::string(std::string("func") + std::string("name")).c_str()
- 它是指向带有字符的内存区域的指针。一些编译器可能会提供“unstringize”字符串的扩展,但我不知道这样的扩展。
在这种情况下,您唯一的选择是编写预处理器或代码生成器,每次构建项目时都会扫描某种文本模板(列出函数),然后将其转换为.h/ .cpp 文件编译到你的项目中。这些 .h/.cpp 文件应该构建函数表(名称到函数指针映射),然后在项目中“在幕后”使用。有关工作示例,请参阅Qt MOC 。每次向模板添加新函数时都需要重新编译。
如果您不想为每个新函数原型重新编译(尽管显然您不能在不重新编译项目的情况下添加对新函数的调用),那么您唯一的选择是将脚本语言嵌入到您的应用程序中。这样您就可以在不重新编译的情况下添加函数。目前,您可以嵌入 lua、python、lisp(via ecl) 和其他语言。还有工作C++ 解释器,虽然我怀疑它是可嵌入的。
如果您不想使用我列出的任何选项,那么(AFAIK)您根本不能这样做。放弃一些要求(“无重新编译”、“无前向声明”、“使用字符串文字调用”)并重试。
我可以使用 C 宏语言将字符串文字可靠地转换为符号名称吗?
不可以。您可以将字符串文字转换为要由编译器处理的标识符(使用 stringize),但如果编译器在编译时不知道此标识符,您的代码将无法编译。因此,如果您要使用它们的名称以这种方式调用函数,那么您必须确保它们之前都是前向声明的。而且您将无法在运行时找到它们。
C++ 不在编译代码中存储函数和变量的名称。因此,您无法通过名称找到已编译的函数。这是因为 C++ 链接器可以完全消除未使用的函数、内联它们或创建多个副本。
你可以做什么:
创建一个要按名称寻址的函数表(将函数名称映射到函数指针),然后使用此表来定位函数。您必须手动注册您希望能够在此表中找到的每个功能。像这样的东西:
typedef std::string FunctionName;
typedef void(*Function)(int arg);
typedef std::map<FunctionName, Function> FunctionMap;
FunctionMap globalFunctionMap;
void callFunction(const std::string &name, int arg){
FunctionMap::iterator found = globalFunctionMap.find(name);
if (found == globalFunctionMap.end()){
//could not find function
return;
}
(*found->second)(arg);
}
使用动态/共享库。将您希望能够寻址的函数放入共享库(extern "C" __declspec(dllexport)
或__declspec(dllexport)
)中,将它们标记为导出,然后使用操作系统函数在库中定位函数(dlsym
在 linux 上,GetProcAddress
在 windows 上)。Afaik,您也可以从 exe 导出函数,因此您可以使用这种方法而无需额外的 dll。
- 将脚本语言嵌入到您的应用程序中。基本上,在大多数脚本语言中,您可以通过名称定位和调用函数。那将是在脚本语言中声明的函数,显然,不是 C++ 函数。
- 编写代码预处理器,它将扫描您的项目以查找“命名”函数并在某处自动构建这些函数的表(方法#1)。可能非常困难,因为 C++ 并不那么容易解析。