详谈C++回调函数
本文最后更新于 88 天前,其中的信息可能已经有所发展或是发生改变。

1. 基本概念

回调函数(Callback Function)是C++编程中一种重要的机制,允许通过函数指针或对象引用间接调用函数。它在事件处理、异步编程、框架设计等领域广泛应用,能够提升代码的灵活性和解耦性。

回调函数本质上也是个函数,不过是用于”回调”的函数。所谓回调,可以理解为将一个函数的调用权交给另一个函数,并在适当的时刻由后者调用前者,而并非普通函数的人为主动调用目标函数。

这种机制的核心思想是解耦异步处理

  • 解耦:主函数(调用方)无需关心回调函数(被调用方)的具体实现,两者的具体实现不相关,主函数只需要关心调用回调函数的时机及后续操作。
  • 异步处理:在主函数一些任务完成或条件满足时(如I/O、GUI事件),自动触发回调函数进行后续操作,调用的时机由主函数确定。


2. 实现方式

需要注意的是类中(非静态)函数对回调函数机制的应用,多使用使用std::functionstd::bind来实现这一机制。对于Qt,则重点使用Qt自身的信号与槽机制即可实现回调机制。

2.1 函数指针

主函数中定义了对应函数指针参数,而回调函数是符合要求的普通函数。在使用主函数时,将回调函数以函数指针的类型传递给主函数,以便主函数调用。

这是最基础也是使用最多的方式,但灵活性较低,需手动管理类型和生命周期。

但是这种方法需要有全局函数或者静态(static)函数

对于类的静态(static)函数也是类似方法调用

示例:

  1. 自定义函数

    void myCallback(){
       printf("myCallback be called!");
    }
    
    void myFunc(void (*callback)()){
       callback();
    }
    
    void main(){
       myFunc(myCallback);
    }
  2. 更多的情况是用于三方类中的已封装的函数

    // 排序函数中使用回调函数
    #include 
    #include 
    
    // 全局函数
    int compareInts(const void* a, const void* b){
       return (*(int*)a - *(int*)b);
    }
    
    void main(){
       std::vector vec = {5, 3, 8, 1};
       std::qsort(vec.data(), vec.size(), sizeof(int), compareInts);
    }

2.2 类中(非静态)函数

非静态成员函数与普通函数不同,它隐含了一个this指针,用于访问类的实例数据。因此,直接将非静态成员函数作为回调函数传递是不可行的,因为它的调用方式不同于普通函数。

常用的有以下几种方法,其中前两种更为常用。

2.2.1 封装为静态成员函数

通过静态成员函数作为“桥梁”,将非静态成员函数与实例绑定。静态函数接收一个指向实例的指针,并手动调用成员函数。

示例:

#include <iostream>

class MyClass {
public:
    // 非静态成员函数,无法直接作为回调函数
    void memberCallback(){
        std::cout << "Non-static member function called!" << std::endl;
    }

    // 静态函数作为回调入口
    static void staticCallback(MyClass* instance){
        instance->memberCallback(); // 转发调用
    }
};

// 主函数接受普通函数指针
void executeCallback(void (*callback)(MyClass*)){
    MyClass obj;
    callback(&obj); // 调用静态函数
}

void main(){
    executeCallback(&MyClass::staticCallback); // 传递静态函数指针
}

2.2.2 使用std::functionstd::bind

通过std::bind绑定成员函数与实例,生成一个可调用对象,再存储到std::function中。用这两个方法则无需在意非静态函数的特殊性。

具体用法和示例可见2.3。

2.2.3 使用 Lambda 表达式捕获对象实例

通过Lambda捕获对象实例(如this指针或对象引用),直接调用成员函数。

示例:

#include <iostream>
#include <functional>

class MyClass{
public:
    void memberCallback(int value){
        std::cout << "Lambda captured this: value = " << value << std::endl;
    }
};

void executeCallback(std::function<void(int)> callback){
    callback(100);
}

void main(){
    MyClass obj;
    // Lambda捕获this指针,并调用成员函数
    executeCallback([thisObj = &obj](int value) {
        thisObj->memberCallback(value);
    });
}

2.2.4 仿函数

通过定义一个具有operator()的类作为函数对象,直接作为回调传递。

示例:

#include <iostream>

class MyFunctor {
private:
    int multiplier;
public:
    MyFunctor(int m) : multiplier(m) {}
    void operator()(int value) {
        std::cout << "Functor result: " << value * multiplier << std::endl;
    }
};

void executeCallback(std::function<void(int)> callback) {
    callback(5); // 调用仿函数
}

int main() {
    MyFunctor functor(10);
    executeCallback(functor); // 输出:Functor result: 50
    return 0;
}

2.2.5 使用 std::mem_fn

std::mem_fn 是 C++11 引入的一个工具,专门用于包装类的成员函数。它可以生成一个可调用对象,并与类实例一起使用。

示例:

#include <iostream>
#include <functional>

class MyClass{
public:
    void nonStaticCallback(const std::string& msg) {
        std::cout << "Non-static callback called with message: " << msg << std::endl;
    }
};

void executeCallback(std::function<void()> callback) {
    callback();
}

void main(){
    MyClass obj;
    // 使用 std::mem_fn 包装非静态成员函数
    auto memFnCallback = std::mem_fn(&MyClass::nonStaticCallback);
    // 将 mem_fn 包装的函数与类实例绑定
    auto boundFunc = std::bind(memFnCallback, &obj, "Hello from mem_fn!");
    // 将绑定后的函数作为回调传递
    executeCallback(boundFunc);
}

2.3 std::functionstd::bind

在C++11中,引入了std::functionstd::bind来存储和绑定函数。

  • std::function:通用的多态函数包装器,可存储任意可调用对象(如普通函数、lambda 表达式、绑定表达式、类成员函数等),但也需要格式一致。
  • std::bind:用于将函数与其部分或全部参数绑定在一起,生成一个新的可调用对象。它允许我们固定某些参数,或者重新排列参数的顺序,适配回调接口。

示例:

#include <functional>
#include <iostream>

class Logger{
public:
    void log(const std::string& msg){
        std::cout << "Log: " << msg << std::endl;
    }
};

// 直接利用std::function来作为参数存储回调函数
void execute(std::function<void()> callback){ 
    callback();
}

void main(){
    Logger logger;
    // 将回调函数的参数绑定起来
    auto boundFunc = std::bind(&Logger::log, &logger, "Hello");
    // 指定主函数,其自动调用回调函数,输出 "Log: Hello"
    execute(boundFunc);
}

2.4 Lambda表达式

直接将Lambda表达式作为回调函数传递,不仅适用于类中非静态函数对象,也适用于更多场景。

得益于Lambda表达式,这种方法简洁、灵活,支持闭包和捕获变量。

示例:

#include <functional>

void executeCallback(std::function<void()> callback){
    callback();
}

void main(){
    int x = 10;
    executeCallback([x]() {
        std::cout << "Value: " << x << std::endl; // 输出10
    });
}

2.5 Qt中信号与槽机制

Qt的信号与槽(Signal and Slot) 是一种专为对象间通信设计的机制,本质上是类型安全的回调函数系统。它通过编译器预处理(moc元对象编译器)实现,解决了传统回调中类型不匹配、对象生命周期管理困难等问题,尤其适合GUI和事件驱动编程。

  • 信号(Signal)

    对象定义的事件触发接口(如按钮的clicked事件)。信号无需实现,仅声明参数列表。

  • 槽(Slot)

    与信号关联的回调函数,用于处理事件。槽可以是普通成员函数、Lambda表达式,甚至其他信号。

  • 连接(Connect)

    通过connect函数将信号与槽绑定,定义事件触发时的执行逻辑。

具体实现逻辑和原理这里略过了,详情可见Qt官方文档。

示例可见3.1。


3. 应用场景

3.1 GUI事件处理

在图形用户界面(GUI)开发中,回调函数常用于处理按钮点击、窗口关闭等事件。

// 假设框架API:void setOnClick(Button*, void(*)(Button*));
void onClick(Button* btn) { btn->setText("Clicked!"); }

Qt中使用信号与槽实现回调:

#include <QApplication>
#include <QPushButton>
#include <QMessageBox>
#include <QWidget>
#include <QVBoxLayout>

class MyWindow : public QWidget {
    Q_OBJECT  // 必须声明以启用信号与槽

public:
    MyWindow() {
        QPushButton* btn = new QPushButton("Click Me", this);
        QVBoxLayout* layout = new QVBoxLayout(this);
        layout->addWidget(btn);

        // 连接按钮的clicked信号到槽函数
        connect(btn, &QPushButton::clicked, this, &MyWindow::onButtonClicked);
    }

private slots:  // 槽函数需在slots部分声明
    void onButtonClicked() {
        QMessageBox::information(this, "Clicked", "Button was clicked!");
    }
};

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
    MyWindow window;
    window.show();
    return app.exec();
}

3.2 异步I/O

在异步编程中,回调函数常用于通知任务完成或处理结果。例如,文件读取完成后调用回调函数处理数据。

// 异步文件读取
#include <iostream>
#include <functional>
#include <thread>
#include <chrono>

// 模拟异步文件读取
void asyncReadFile(const std::string& filename, std::function<void(const std::string&)> callback){
    std::thread([filename, callback](){
        std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
        std::string data = "File content of " + filename;
        callback(data); // 调用回调函数
    }).detach();
}

class FileProcessor{
public:
    void processFile(const std::string& filename){
        asyncReadFile(filename, [this](const std::string& data) {
            this->onFileProcessed(data); // 回调处理数据
        });
    }

private:
    void onFileProcessed(const std::string& data){
        std::cout << "Processing file: " << data << std::endl;
    }
};

void main(){
    FileProcessor processor;
    processor.processFile("example.txt");
    std::this_thread::sleep_for(std::chrono::seconds(3)); // 等待异步操作完成
}

3.3 网络请求回调

处理HTTP请求的响应。

#include <functional>

class NetworkClient{
public:
    void sendRequest(const std::string& url, std::function<void(const std::string&)>&& callback){
        // 异步发送请求,完成后调用callback(response)
    }
};

void main(){
    NetworkClient client;
    client.sendRequest("https://api.example.com/data", [](const std::string& data){
        std::cout << "Received data: " << data << std::endl;
    });
}

3.4 多线程任务完成回调

通过std::futurestd::promise实现线程间结果传递。

#include <future>
#include <iostream>
#include <thread>

void computeTask(std::promise<int>& result){
    int value = 42;
    result.set_value(value);
}

void main(){
    std::promise<int> resultPromise;
    std::future<int> resultFuture = resultPromise.get_future();

    std::thread worker(computeTask, std::ref(resultPromise));
    std::cout << "Result: " << resultFuture.get() << std::endl; // 输出42
    worker.join();
}

3.5 事件监听器模式

实现观察者模式,解耦事件生产者和消费者。

#include <vector>
#include <functional>

class EventManager{
public:
    void addListener(const std::function<void()>& listener){
        listeners.push_back(listener);
    }
    void triggerEvent() {
        for (auto& listener : listeners){
            listener();
        }
    }
private:
    std::vector<std::function<void()>> listeners;
};

void main(){
    EventManager em;
    em.addListener([]() { std::cout << "Button clicked!" << std::endl; });
    em.addListener([]() { std::cout << "Mouse moved!" << std::endl; });
    em.triggerEvent();
}

3.6 数据流处理

在数据流处理中,回调函数可以用于处理数据管道中的每个阶段。

#include <iostream>
#include <functional>
#include <vector>

class DataPipeline {
private:
    std::vector<std::function<int(int)>> stages;

public:
    void addStage(std::function<int(int)> stage){
        stages.push_back(stage);
    }

    int process(int input){
        for (auto& stage : stages){
            input = stage(input); // 调用每个阶段的回调函数
        }
        return input;
    }
};

void main(){
    DataPipeline pipeline;

    pipeline.addStage([](int x) { return x * 2; }); // 阶段1:乘以2
    pipeline.addStage([](int x) { return x + 10; }); // 阶段2:加10
    pipeline.addStage([](int x) { return x / 3; }); // 阶段3:除以3

    int result = pipeline.process(5); // 输入5,输出结果
    std::cout << "Result: " << result << std::endl; // 输出:8
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇