在日常编码过程中,一般使用的回调函数的参数类型和数目是固定的,但是在需要根据不同枚举类型注册不同回调函数的场景下,会存在参数类型和数目不固定的情况,本文根据可变参数模板和std::function,实现可变参数回调函数的注册与调用。
首先定义一个类A,包含:注册回调函数(registCB),第一个参数是枚举类,第二个参数是利用template<typename... Args> 定义的一个std::function函数包装模板,参数不同的回调在此函数中进行注册。回调获取函数(getCB),通过传入的枚举获取对应回调。回调测试函数(callBackTest),是我用来进行测试的函数。这里需要注意的是:类A并没有定义成模板类,所以私有变量callBackFuncs_ 是变量模板,这是从C++14开始支持的特性。
#include <string>
#include <iostream>
#include <vector>
#include <map>
#include <memory>
#include <thread>
#include <functional>
#include <windows.h>
using namespace std;
using namespace std::placeholders;
class A {
public:
A() {
std::cout << "create A" << std::endl;
}
~A() {
std::cout << "delete A" << std::endl;
}
// 此处用模板,注册不同的回调函数 Args...
template<typename... Args>
void registCB(int32_t CBType, std::function<void(Args...)> callBackFunc);
// 此处用模板,获取不同的回调函数 Args...
template<typename... Args>
std::function<void(Args...)> getCB(int32_t CBType);
// 调用回调函数
void callBackTest();
private:
// 注册上的回调函数,非模板类情况下设置为静态成员变量
// 单独的模板变量是C++14开始支持的
template<typename... Args>
static std::map<int32_t, std::function<void(Args...)> > callBackFuncs_;
};
然后编写类A中函数的具体实现,在注册和获取回调的时候,要注意写上可变参数模板,这与实际调用这两个函数时,所需要设置的实际参数类型是对应的。可以看到callBackTest 在调用具体回调的时候,就要把此处的参数个数和类型进行明确了。
template<typename... Args>
std::map<int32_t, std::function<void(Args...)> > A::callBackFuncs_;
template<typename... Args>
void A::registCB(int32_t CBType, std::function<void(Args...)> callBackFunc) {
callBackFuncs_<Args...>[CBType] = (callBackFunc);
}
template<typename... Args>
std::function<void(Args...)> A::getCB(int32_t CBType) {
return callBackFuncs_<Args...>[CBType];
}
void A::callBackTest() {
auto twoCBFunc = getCB<std::string, int32_t>(2);
if (twoCBFunc) {
std::cout << "execute two callback func!" << endl;
twoCBFunc("laotie", 666);
}
auto oneCBFunc = getCB<int32_t>(1);
if (oneCBFunc) {
std::cout << "execute one callback func!" << endl;
oneCBFunc(2333);
}
}
我又定义了一个类B,这个类用于编写具体的回调函数,这里写了两个回调函数用于之后的测试,它们具有不同的入参数量、类型和相同的返回。因为回调函数是类B的成员函数,所以在注册回调的时候可以使用std::bind进行绑定。
class B {
public:
B() {
std::cout << "create B" << std::endl;
}
~B() {
std::cout << "delete B" << std::endl;
}
// 回调函数1,入参为int
void testCBOne(int32_t testInt);
// 回调函数2,入参为string和int
void testCBTwo(std::string testStr, int32_t testInt);
};
void B::testCBOne(int32_t testInt) {
std::cout << "HAHAHA: " << testInt << std::endl;
}
void B::testCBTwo(std::string testStr, int32_t testInt) {
std::cout << testStr << " double-click " << testInt << std::endl;
}
最后在main函数中进行回调的注册和调用,可以看到,在根据枚举值注册具体的回调时,要明确对应的参数类型和个数,不然在编译过程中,编译器是无法找到具体的回调函数,会直接进行报错。
int main() {
shared_ptr<A> testA = make_shared<A>();
shared_ptr<B> testB = make_shared<B>();
// 注册不同的回调函数
auto oneFunc = std::bind(&B::testCBOne,
testB,
::_1);
testA->registCB(1, std::function<void(
int32_t)>(oneFunc));
auto decodeFunc = std::bind(&B::testCBTwo,
testB,
::_1, ::_2);
testA->registCB(2, std::function<void(
std::string,
int32_t)>(decodeFunc));
testA->callBackTest();
getchar();
return 0;
}
将整段代码进行运行,可以看到输入不同的枚举类型时,可以调用对应的回调进行不同参数类型和个数的处理。虽然注册回调函数是同一个,但是在实际注册的时候还是要指定具体的参数类型和个数的,不会实现完全的可变,不然编译器是无法进行对应操作的。
记录一个C++回调的小tip:
之前写C的回调函数,或者与别的语言进行接口调用的时候,都是通过函数指针的形式实现。但是C++有类的概念,其中通过“继承”这一面向对象的特性,可以实现C++风格的灵活回调。
我们首先定义一个回调函数抽象类AbstractCB,这个类中包含一个纯虚函数,即回调函数onAbstractCB(int32_t num):
class AbstractCB {
public:
AbstractCB() {}
virtual ~AbstractCB() {}
public:
virtual void onAbstractCB(int32_t num) = 0;
};
然后定义一个操作类Operation,这个类对数据进行一些操作,并对结果进行回调。Operation类包含注册回调函数和数据处理函数:
class Operation{
public:
Operation() {
is_run_ = true;
}
~Operation() {
is_run_ = false;
}
public:
void StartOperation() {
operation_thread_ = new std::thread([this]() {this->OperationData();});
operation_thread_.detach();
}
void RegistOperationCB(AbstractCB* callback) {
operation_CB_ = callback;
}
private:
void OperationData() {
uint32_t i = 0;
while(is_run_) {
if(0 == i%100) {
operation_CB_->onAbstractCB();
}
i++;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
private:
bool is_run_;
// 数据操作线程
std::thread operation_thread_;
// 回调函数类
AbstractCB* operation_CB_;
};
这样当我们想要注册回调函数到Operation类的时候,只需要创建一个新的类,继承AbstractCB 这个基类,然后在需要注册的地方,通过调用RegistOperationCB将自身类对象的指针传给Operation 类即可:
class SubClass : public AbstractCB {
public:
SubClass() {}
~SubClass() {}
public:
void Start() {
Operation_ = std::make_shared<Operation>();
Operation_->RegistOperationCB(this);
}
void onAbstractCB(int32_t num) {
std::cout << "Test callback result: " << num << std::endl;
}
shared_ptr<Operation> operation_;
}