如何检查 C/C++ 中某个函数是否存在?
- 2024-11-13 08:36:00
- admin 原创
- 21
问题描述:
在我的代码中,某些情况下,我只会在定义了该函数时才调用该函数,否则就不应该调用。我该如何实现这一点?
like:
if (function 'sum' exists ) then invoke sum ()
也许反过来问这个问题是如何确定函数是否在运行时定义,如果是,那么调用?
解决方案 1:
使用 GCC 您可以:
void func(int argc, char *argv[]) __attribute__((weak)); // weak declaration must always be present
// optional definition:
/*void func(int argc, char *argv[]) {
printf("FOUND THE FUNCTION
");
for(int aa = 0; aa < argc; aa++){
printf("arg %d = %s
", aa, argv[aa]);
}
}*/
int main(int argc, char *argv[]) {
if (func){
func(argc, argv);
} else {
printf("did not find the function
");
}
}
如果取消注释 func,它将运行它,否则它将打印“未找到该函数\n”。
解决方案 2:
当您声明“sum”时,您可以像这样声明:
#define SUM_EXISTS
int sum(std::vector<int>& addMeUp) {
...
}
然后当你使用它时你可以这样做:
#ifdef SUM_EXISTS
int result = sum(x);
...
#endif
我猜你来自一种脚本语言,其中所有事情都是在运行时完成的。使用 C++ 时要记住的主要事情是两个阶段:
编译时
预处理器运行
模板代码变成真正的源代码
源代码转换成机器代码
运行时
运行机器代码
所以所有#define
类似的事情都发生在编译时。
....
如果您真的想在运行时完成所有操作,您可能会对使用一些组件架构产品感兴趣。
或者也许您追求的是插件类型的架构。
解决方案 3:
虽然其他回复都是有用的建议(dlsym
、函数指针等),但您无法编译引用不存在的函数的 C++ 代码。至少,必须声明该函数;如果没有,您的代码将无法编译。如果没有任何东西(编译单元、某个目标文件、某个库)定义该函数,链接器就会抱怨(除非它很弱,见下文)。
但你确实应该解释一下你为什么问这个问题。我猜不出来,而且一定有某种方法可以实现你未说明的目标。
请注意,dlsym
通常需要没有名称修改的函数,即声明为extern "C"
。
如果在 Linux 上使用 GCC 进行编码,您可能还会在声明中使用weak
函数属性。然后链接器会将未定义的弱符号设置为空。
附加物
如果您从某些输入中获取函数名称,则应该知道只有一部分函数可以通过这种方式调用(如果您不小心调用任意函数,它就会崩溃!),最好明确构造该子集。然后,您可以使用std::map
,或dlsym
(子集中的每个函数都声明为extern "C"
)。请注意,dlopen
使用NULL
路径会为主程序提供句柄,您应该将其链接起来-rdynamic
以使其正常工作。
您确实希望仅通过其名称调用适当定义的函数子集。例如,您可能不想以这种方式调用abort
、exit
或fork
。
注意:如果您动态地知道被调用函数的签名,则可能需要使用libffi来调用它。
解决方案 4:
我怀疑发帖人实际上正在寻找更多类似于 SFINAE 检查/调度的东西。使用 C++ 模板,可以定义两个模板函数,一个调用所需函数(如果存在),另一个不执行任何操作(如果函数不存在)。然后,您可以使第一个模板依赖于所需函数,这样当函数不存在时,模板格式不正确。这是有效的,因为在 C++ 中模板替换失败不是错误(SFINAE),因此编译器将返回到第二种情况(例如,什么也不做)。
请参阅此处的一个很好的例子:是否可以编写模板来检查函数是否存在?
解决方案 5:
使用指向函数的指针。
//initialize
typedef void (*PF)();
std::map<std::string, PF> defined_functions;
defined_functions["foo"]=&foo;
defined_functions["bar"]=&bar;
//if defined, invoke it
if(defined_functions.find("foo") != defined_functions.end())
{
defined_functions["foo"]();
}
解决方案 6:
如果您知道想要调用的函数在哪个库中,那么您可以使用dlsym()
和dlerror()
来查明它是否存在,以及指向该函数的指针是什么。
编辑:我可能不会真正使用这种方法 - 相反,我会推荐 Matiu 的解决方案,因为我认为这是一个更好的做法。但是,dlsym()
它并不为人所知,所以我想指出这一点。
解决方案 7:
您可以使用#pragma weak
支持它的编译器(参见弱符号维基百科条目)。
此示例和评论来自共享库和动态加载的内幕故事:
#pragma weak debug
extern void debug(void);
void (*debugfunc)(void) = debug;
int main() {
printf(“Hello World
”);
if (debugfunc) (*debugfunc)();
}
您可以使用弱指令强制链接器忽略未解析的符号[..],无论 debug() 是否在任何目标文件中实际定义,程序都会编译和链接。当符号仍未定义时,链接器通常会将其值替换为 0。因此,对于程序来说,这种技术可以成为一种有用的方法,用于调用不需要重新编译整个应用程序的可选代码。
解决方案 8:
因此,如果您使用 c++11,另一种方法是使用函子:
您需要将其放在文件的开头:
#include <functional>
函子的类型声明格式如下:
std::function< return_type (param1_type, param2_type) >
您可以添加一个保存 sum 函子的变量,如下所示:
std::function<int(const std::vector<int>&)> sum;
为了简单起见,我们缩短参数类型:
using Numbers = const std::vectorn<int>&;
然后你可以用以下任何一个来填充函子var:
lambda表达式:
sum = [](Numbers x) { return std::accumulate(x.cbegin(), x.cend(), 0); } // std::accumulate comes from #include <numeric>
函数指针:
int myFunc(Numbers nums) {
int result = 0;
for (int i : nums)
result += i;
return result;
}
sum = &myFunc;
“bind” 创建了一些内容:
struct Adder {
int startNumber = 6;
int doAdding(Numbers nums) {
int result = 0;
for (int i : nums)
result += i;
return result;
}
};
...
Adder myAdder{2}; // Make an adder that starts at two
sum = std::bind(&Adder::doAdding, myAdder);
最后使用它,这是一个简单的 if 语句:
if (sum)
return sum(x);
总结一下,函子是指向函数的新指针,但它们的用途更广泛。如果编译器足够确定,实际上可以内联,但通常与函数指针相同。
当与 std::bind 和 lambda 结合时,它们比旧式 C 函数指针要优越得多。
但请记住它们在 c++11 及以上环境中有效。(不适用于 C 或 C++03)。
解决方案 9:
在 C++ 中,检查成员是否存在的技巧的修改版本应该在编译时而不是运行时为您提供您想要的内容:
#include <iostream>
#include <type_traits>
namespace
{
template <class T, template <class...> class Test>
struct exists
{
template<class U>
static std::true_type check(Test<U>*);
template<class U>
static std::false_type check(...);
static constexpr bool value = decltype(check<T>(0))::value;
};
template<class U, class = decltype(sum(std::declval<U>(), std::declval<U>()))>
struct sum_test{};
template <class T>
void validate_sum()
{
if constexpr (exists<T, sum_test>::value)
{
std::cout << "sum exists for type " << typeid(T).name() << '
';
}
else
{
std::cout << "sum does not exist for type " << typeid(T).name() << '
';
}
}
class A {};
class B {};
void sum(const A& l, const A& r); // we only need to declare the function, not define it
}
int main(int, const char**)
{
validate_sum<A>();
validate_sum<B>();
}
以下是使用 clang 的输出:
sum exists for type N12_GLOBAL__N_11AE
sum does not exist for type N12_GLOBAL__N_11BE
我应该指出,当我使用 int 而不是 A 时,发生了奇怪的事情(sum()
必须事先声明sum_test
才能exists
工作,所以可能exists
不是这个的正确名称)。当我使用 A 时,某种模板扩展似乎不会引起问题。我猜它与 ADL 有关。
解决方案 10:
在 c 中,你可以使用函数指针数组
#include<stdio.h>
#include<string.h>
typedef struct i32_2arg{int a, b;}i32_2_arg;
typedef struct i32_arg{int a;}i32_arg;
void add(void*a,void*r){
i32_2_arg* l = (i32_2_arg*)a;
((i32_arg*)r)->a = l->a+l->b;
}
void sub(void*a,void*r){}
char lFunNames[8][64] = {"add","sub",0};
void (*lFuns[8]) (void*,void*) = {&add,&sub,0};
void evalfun(char* lName, void* inargs,void* outargs){
for(int i = 0; i < 8; i++ )
if (!strcmp(lFunNames[i],lName)) (*lFuns[i])(inargs,outargs);
}
int main(){
i32_2_arg ab ={2,3};
i32_arg sum;
evalfun("add",&ab,&sum);
printf("if \"add\" exists, result is %d
",sum.a);
return 0;
}
解决方案 11:
此答案针对全局函数,作为对测试方法的其他答案的补充。此答案仅适用于全局函数。
首先,在单独的命名空间中提供一个 fallback 虚拟函数。然后在模板参数中确定函数调用的返回类型。根据返回类型,确定这是 fallback 函数还是所需函数。
如果禁止在函数的命名空间中添加任何内容,例如 for 的情况std::
,那么您应该使用ADL在测试中找到正确的函数。
例如,std::reduce()
是 c++17 的一部分,但早期的 gcc 编译器(应该支持 c++17)没有定义std::reduce()
。以下代码可以在编译时检测是否std::reduce
声明。在编译资源管理器中查看它在两种情况下是否正常工作。
#include <numeric>
namespace fallback
{
// fallback
std::false_type reduce(...) { return {}; }
// Depending on
// std::recuce(Iter from, Iter to) -> decltype(*from)
// we know that a call to std::reduce(T*, T*) returns T
template <typename T, typename Ret = decltype(reduce(std::declval<T*>(), std::declval<T*>()))>
using return_of_reduce = Ret;
// Note that due to ADL, std::reduce is called although we don't explicitly call std::reduce().
// This is critical, since we are not allowed to define any of the above inside std::
}
using has_reduce = fallback::return_of_reduce<std::true_type>;
// using has_sum = std::conditional_t<std::is_same_v<fallback::return_of_sum<std::true_type>,
// std::false_type>,
// std::false_type,
// std::true_type>;
#include <iterator>
int main()
{
if constexpr (has_reduce::value)
{
// must have those, so that the compile will find the fallback
// function if the correct one is undefined (even if it never
// generates this code).
using namespace std;
using namespace fallback;
int values[] = {1,2,3};
return reduce(std::begin(values), std::end(values));
}
return -1;
}
与上述示例不同,当您无法控制返回类型时,可以使用其他方法,例如std::is_same
和std::contitional
。
例如,假设您要测试函数是否int sum(int, int)
在当前编译单元中声明。以类似的方式创建。test_sum_ns::return_of_sum
如果函数存在,则为,否则int
为std::false_type
(或您喜欢的任何其他特殊类型)。
using has_sum = std::conditional_t<std::is_same_v<test_sum_ns::return_of_sum,
std::false_type>,
std::false_type,
std::true_type>;
然后您可以使用该类型:
if constexpr (has_sum::value)
{
int result;
{
using namespace fallback; // limit this only to the call, if possible.
result = sum(1,2);
}
std::cout << "sum(1,2) = " << result << '
';
}
注意:必须有using namespace
,否则编译器将找不到 中的 fallback 函数if constexpr
,并会报错。一般来说,你应该避免,using namespace
因为命名空间内符号的未来变化可能会破坏你的代码。在这种情况下,没有其他办法,所以至少要将其限制在尽可能小的范围,如上例所示
解决方案 12:
这是另一种方法:将可选方法声明为结构内的虚方法,然后使用该结构作为实现该方法的派生对象的基类。
示例(包括用例概要):
#include <map> // For std::map
#include <string> // For std::string
#include <stdio.h> // For printf
//----------------------------------------------------------------------------
class Basic {
public:
typedef std::map<std::string, Basic*> Map_t; // The Map type
static Map_t map; // The Global map
Basic(std::string name)
{ map[name]= this; }
virtual
~Basic( void )
{ }
// Optional methods- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct has_stop {
virtual void
stop( void )
{ }
}; // struct has_stop
struct has_wait {
virtual void
wait( void )
{ }
}; // struct has_wait
}; // class Basic
Basic::Map_t Basic::map;
//----------------------------------------------------------------------------
class Global {
public:
void
stop( void )
{
Basic::Map_t& map= Basic::map;
for(auto const& mi: map) {
Basic::has_stop* method= dynamic_cast<Basic::has_stop*>(mi.second);
if( method )
method->stop();
else
printf("Basic(%s) doesn't have stop()
", mi.first.c_str());
}
}
void
wait( void )
{
Basic::Map_t& map= Basic::map;
for(auto const& mi: map) {
Basic::has_wait* method= dynamic_cast<Basic::has_wait*>(mi.second);
if( method )
method->wait();
else
printf("Basic(%s) doesn't have wait()
", mi.first.c_str());
}
}
}; // class Global
static Global global;
//----------------------------------------------------------------------------
class One : public Basic {
public:
One(std::string name)
: Basic(name) {}
// While the stop method is present, has_stop is not a base class.
// Global::stop won't find the method.
virtual void stop( void )
{ printf("One::stop
"); }
}; // class One
//----------------------------------------------------------------------------
class Two
: public Basic
, public Basic::has_stop, public Basic::has_wait {
public:
Two(std::string name)
: Basic(name) {}
virtual void stop( void ) // (Overrides Basic::has_stop)
{ printf("Two::stop
"); }
virtual void wait( void ) // (Overrides Basic::has_wait)
{ printf("Two::wait
"); }
}; // class Two
//----------------------------------------------------------------------------
int main( void ) {
One one("one");
Two two("two");
one.stop(); // We can invoke One::stop()
global.stop(); // But Global::stop won't find it
global.wait();
return 0;
} // main
输出:
One::stop
Basic(one) doesn't have stop()
Two::stop
Basic(one) doesn't have wait()
Two::wait
另一个(更简单的)选项是始终将函数包含在基类中,并且基类实现为空,除了返回函数签名所需的内容。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件