回调本身有多种形式,下面一一介绍

封装式/延迟回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Task{
public:
typedef void (*TaskCallback)(void*);//为 ‘一个指向“返回值为void,参数为void*”的函数’ 的指针类型,创建一个新的类型别名,名为 TaskCallback。”
Task():mTaskCallback(NULL),mArg(NULL) {};//当你创建一个 Task 对象时,它内部的函数指针 mTaskCallback 和参数指针 mArg 都被初始化为 NULL。

void setTaskCallback(TaskCallback cb, void* arg) {
mTaskCallback = cb; mArg = arg;
}

void handle() {
if(mTaskCallback)
mTaskCallback(mArg);
}

bool operator=(const Task& task) {
this->mTaskCallback = task.mTaskCallback;
this->mArg = task.mArg;
}
private:
TaskCallback mTaskCallback;
void* mArg;
};

在上面的回调函数定义中,首先定义别名,具体见上,其中void*是一个通用指针,可以指向任何类型数据。为了充分说明,我们定义一个处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 假设有一个连接信息的结构体
struct ConnectionInfo {
int socket_fd;
std::string client_ip;
};

// 定义一个符合 TaskCallback 签名的函数
void handle_connection(void* arg) {
// 将 void* 转换回原始类型
ConnectionInfo* info = static_cast<ConnectionInfo*>(arg);

printf("Handling connection from IP: %s on socket %d\n",
info->client_ip.c_str(), info->socket_fd);

// ... 在这里执行具体的读写操作 ...

// 清理资源
delete info;
}

接下来通过set方法装载,myTask.setTaskCallback(handle_connection, connectionData);,执行完这步后,myTask 对象内部的 mTaskCallback 指向了 handle_connection 函数,mArg 指向了 connectionData 对象的内存地址。

需要的时候,系统的其他部分(比如一个线程池或者事件循环)只需要调用 Task 对象的 handle() 方法,就可以执行被封装的任务,而无需关心任务具体是什么。

这种模式非常适用于:

1.线程池/任务队列:主线程创建一堆 Task 对象(通过 set 配置好),然后把这些对象扔进一个队列。工作线程不断从队列中取出 Task 对象,并调用它们的 handle() 方法。 2.事件驱动编程:当某个事件发生时,事件循环调用对应事件处理程序(一个 Task 对象)的 handle() 方法。 3.实现解耦:调用 handle() 的代码完全不知道它到底在执行哪个具体函数,它只知道如何执行一个 Task。

普通回调

这种方式就是网上非常常见的,这里不做赘述,贴上gpt代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

// 这是一个回调函数,告诉qsort如何比较两个整数
int compare_integers(const void* a, const void* b) {
int val1 = *(int*)a;
int val2 = *(int*)b;
return (val1 - val2);
}

int main() {
int numbers[] = {5, 2, 8, 1, 9};
int n = sizeof(numbers) / sizeof(numbers[0]);

// 调用qsort,直接把回调函数 compare_integers 作为参数传进去
qsort(numbers, n, sizeof(int), compare_integers);

for(int i = 0; i < n; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

return 0;
}

现代C++中的lamda与function

  • std::function: 一个通用的、多态的函数封装器。它可以存储、复制和调用任何“可调用对象”(普通函数、Lambda表达式、成员函数、函数对象等)。它完美替代了 void (TaskCallback)(void)。

  • Lambda表达式: 一种在源代码中定义匿名函数的方式。它可以方便地“捕获”上下文中的变量,从而完全避免使用不安全的 void*。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <functional> // 需要包含这个头文件
#include <string>

class ModernTask {
public:
// 使用 std::function,它可以持有任何无参数、无返回值的可调用对象
// 这比C风格的函数指针更强大、更安全
using TaskCallback = std::function<void()>;

ModernTask() = default; // 使用默认构造函数

// set函数现在接受一个 std::function 对象
void setTask(TaskCallback cb) {
mTaskCallback = cb;
}

// handle函数保持不变
void handle() {
if (mTaskCallback) { // 检查是否有效
mTaskCallback(); // 直接调用
}
}

private:
TaskCallback mTaskCallback;
};

// 使用示例
struct ConnectionInfo {
int socket_fd;
std::string client_ip;
};

int main() {
ModernTask myTask;
ConnectionInfo connectionData = {10, "192.168.1.101"};

// 使用Lambda表达式来创建回调
// [=] 表示按值捕获所有外部变量 (这里是connectionData)
myTask.setTask([=]() {
// 在Lambda内部,可以直接访问 connectionData,无需 void* 转换
std::cout << "Handling connection from IP: " << connectionData.client_ip
<< " on socket " << connectionData.socket_fd << std::endl;
});

myTask.handle();

return 0;
}

本站由 Edison.Chen 创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

undefined