场景
注册回调函数,判断是否是重复注册
方法
C++中针对std::function类型数据,可以通过如下的方式判断
比如using RecvCb_t = std::function<void(const uint8_t *, size_t)>;
using RecvCb_t = std::function<void(const uint8_t *, size_t)>;
void test_func1(const uint8_t *data, size_t size) {
std::cout<<"func1"<<std::endl;
return;
}
void test_func2(const uint8_t *data, size_t size) {
std::cout<<"func2"<<std::endl;
return;
}
RecvCb_t func1 = test_func1;
RecvCb_t func2 = test_func2;
if (func1.target<void (*)(const uint8_t *, size_t)>() == func2.target<void (*)(const uint8_t *, size_t)>()) {
std::cout<<"It's same func"<<std::cout;
}else{
std::cout<<"It's different func"<<std::cout;
}
上面会输出"It's same func",可以正确判断是不同的两个函数
但如果
RecvCb_t func1 = std::bind(&test_func1, std::placeholders::_1, std::placeholders::_2);
RecvCb_t func2 = std::bind(&test_func2, std::placeholders::_1, std::placeholders::_2);
就会输出"It's different func",别错误的识别为了同一个函数
原因分析
虽然 test_func1 和 test_func2 是两个不同的函数,但是当你使用 std::bind
并使用 std::placeholders
占位符之后,std::bind
返回的是一个函数对象,并且这两个函数对象的类型是相同的。
在使用了 func1.target<void (*)(const uint8_t *, size_t)>()
和 func1.target<void (*)(const uint8_t *, size_t)>()
来获取函数指针并进行比较。由于类型擦除机制,std::bind
返回的函数对象在进行 target<void (*)(const uint8_t *, size_t)>()
操作时,会得到相同的函数指针类型,导致判断错误。
解决方法
为了解决这个问题,你需要区分通过 std::bind
生成的不同函数对象。一种可行的方案是:
为每个回调函数定义一个包装类:也就是仿函数,函数对象
class TestFunc1 {
public: void operator()(const uint8_t * data, size_t len) {
test_func1(data, len);
}
};
class TestFunc2 {
public: void operator()(const uint8_t * data, size_t len) {
test_func2(data, len);
}
};
RecvCb_t func1 = TestFunc1();
RecvCb_t func2 = TestFunc2();
仿函数也称为函数对象是 C++ 中的一种编程概念,指的是行为类似函数的对象。本质上,仿函数是一个定义了 operator()
运算符的类,使得该类的对象可以像函数一样被调用。
仿函数的特点:
- 像函数一样调用: 仿函数对象可以使用函数调用语法
()
进行调用。 - 可以拥有状态: 与普通函数不同,仿函数可以拥有自己的成员变量,用于存储状态或配置信息。
- 可以作为函数参数传递: 仿函数可以作为参数传递给其他函数,例如 STL 算法函数。
- 类型安全: 编译器会对仿函数的参数类型进行检查,提高了代码的类型安全性。
仿函数的优点:
- 更灵活的参数绑定: 可以使用
std::bind
等工具方便地绑定参数,生成新的仿函数。 - 可以拥有状态: 可以保存每次调用的状态,方便实现一些有状态的算法。
- 提高代码可读性: 通过定义语义化的仿函数,可以提高代码的可读性和可维护性。
仿函数的应用场景:
- STL 算法库: STL 中大量使用了仿函数,例如
std::sort
,std::for_each
等算法函数,可以接受仿函数作为参数来自定义排序规则或操作逻辑。 - 回调函数: 仿函数可以作为回调函数,用于事件处理、异步编程等场景。
- 实现策略模式: 仿函数可以用于实现策略模式,将不同的算法封装在不同的仿函数类中。
示例:
#include <iostream>
// 定义一个仿函数类,用于比较两个整数的大小
struct CompareGreater {
bool operator()(int a, int b) const {
return a > b;
}
};
int main() {
CompareGreater compare;
int x = 5, y = 3;
// 使用仿函数对象进行比较
if (compare(x, y)) {
std::cout << x << " 大于 " << y << std::endl;
} else {
std::cout << x << " 小于等于 " << y << std::endl;
}
return 0;
}
在这个例子中,CompareGreater
类就是一个仿函数类,它定义了 operator()
运算符,用于比较两个整数的大小。通过创建 CompareGreater
类的对象,我们可以像调用函数一样使用它来进行比较操作。