在 C++ 中,类与类之间的函数回调是实现类间通信的重要方式(例如 A 类触发事件后通知 B 类处理)。根据类之间的关系和使用场景,主要有以下几种实现方式:

一、通过函数指针(全局函数/静态成员函数)作为桥梁

这是最基础的方式:一个类(被回调方)提供全局函数或静态成员函数作为回调入口,另一个类(调用方)通过函数指针传递回调,并在触发时调用。

示例:B 类回调 A 类的方法
#include <iostream>

// 被回调的类
class A {
public:
    void handleEvent(int data) {
        std::cout << "A 处理事件,数据: " << data << std::endl;
    }

    // 静态成员函数(作为回调桥梁)
    static void staticCallback(A* instance, int data) {
        if (instance) {
            instance->handleEvent(data); // 调用非静态成员
        }
    }
};

// 发起回调的类
class B {
public:
    // 存储函数指针(参数包含A的实例指针)
    using Callback = void (*)(A*, int);
    Callback callback_;
    A* a_instance_;

    // 注册回调(关联A的实例和静态函数)
    void setCallback(A* a, Callback cb) {
        a_instance_ = a;
        callback_ = cb;
    }

    // 触发回调
    void triggerEvent(int data) {
        if (callback_) {
            callback_(a_instance_, data); // 调用A的回调
        }
    }
};

int main() {
    A a;
    B b;
    b.setCallback(&a, &A::staticCallback); // 注册回调
    b.triggerEvent(100); // 触发,输出:A 处理事件,数据: 100
    return 0;
}
优点
  • 简单直接,兼容性好(可与 C 代码交互)。
  • 性能开销小,适合对性能敏感的场景。
缺点
  • 需要静态成员函数作为中间层,不够直观。
  • 函数指针类型固定,灵活性低(若回调参数变化,需修改多处)。

二、通过 std::function + lambda(现代 C++ 推荐)

利用 std::function 包装回调逻辑,配合 lambda 表达式捕获类实例,实现类间回调的灵活性。

示例:B 类回调 A 类,支持任意签名
#include <iostream>
#include <functional>

// 被回调的类
class A {
public:
    void onMessage(const std::string& msg) {
        std::cout << "A 收到消息: " << msg << std::endl;
    }
};

// 发起回调的类
class B {
public:
    // 用std::function存储回调(支持任意可调用对象)
    using Callback = std::function<void(const std::string&)>;
    Callback callback_;

    // 注册回调
    void setCallback(Callback cb) {
        callback_ = cb;
    }

    // 触发回调
    void sendMessage(const std::string& msg) {
        if (callback_) {
            callback_(msg); // 调用回调
        }
    }
};

int main() {
    A a;
    B b;

    // 注册回调:lambda捕获A的实例,调用其成员函数
    b.setCallback([&a](const std::string& msg) {
        a.onMessage(msg);
    });

    b.sendMessage("Hello from B"); // 触发,输出:A 收到消息: Hello from B
    return 0;
}
优点
  • 灵活性极高:支持任意函数签名,无需静态成员函数。
  • 代码简洁:通过 lambda 直接捕获类实例,无需显式传递 this 指针。
  • 类型安全:编译期检查回调签名是否匹配。
缺点
  • 存在轻微的性能开销(类型擦除机制),但远小于代码维护成本。
  • 依赖 C++11 及以上标准库。

三、通过接口(纯虚函数)实现回调(观察者模式)

定义一个抽象接口类,被回调的类实现该接口,调用方通过接口指针触发回调。这是设计模式中“观察者模式”的典型实现。

示例:基于接口的观察者模式
#include <iostream>
#include <vector>

// 回调接口(纯虚函数)
class ICallback {
public:
    virtual void onEvent(int code) = 0; // 纯虚函数,子类必须实现
    virtual ~ICallback() = default; // 虚析构
};

// 被回调的类(实现接口)
class A : public ICallback {
public:
    void onEvent(int code) override { // 实现接口方法
        std::cout << "A 处理事件,代码: " << code << std::endl;
    }
};

// 另一个被回调的类(也实现接口)
class C : public ICallback {
public:
    void onEvent(int code) override {
        std::cout << "C 处理事件,代码: " << code << std::endl;
    }
};

// 发起回调的类(可通知多个观察者)
class B {
private:
    std::vector<ICallback*> observers_; // 存储接口指针

public:
    // 注册观察者(添加回调)
    void addObserver(ICallback* observer) {
        observers_.push_back(observer);
    }

    // 触发事件,通知所有观察者
    void trigger(int code) {
        for (auto obs : observers_) {
            obs->onEvent(code); // 调用接口方法(多态)
        }
    }
};

int main() {
    A a;
    C c;
    B b;

    b.addObserver(&a); // A 注册为观察者
    b.addObserver(&c); // C 注册为观察者

    b.trigger(200); // 触发事件,A和C都会被回调
    // 输出:
    // A 处理事件,代码: 200
    // C 处理事件,代码: 200
    return 0;
}
优点
  • 规范性强:通过接口定义明确回调契约,符合面向对象设计原则。
  • 支持多观察者:可同时回调多个实现了接口的类(一对多通信)。
  • 类型安全:编译期确保回调方法存在,无函数指针的类型风险。
缺点
  • 需定义接口类,略增加代码量。
  • 回调签名固定(接口方法的签名),修改需调整接口。

四、通过成员函数指针直接回调

C++ 支持指向类成员函数的指针(T::* 语法),可直接调用类的非静态成员函数,但需要显式传递实例指针。

示例:使用成员函数指针
#include <iostream>

class A {
public:
    void func(int data) {
        std::cout << "A::func 被调用,数据: " << data << std::endl;
    }
};

class B {
public:
    // 定义A的成员函数指针类型
    using AMemberFunc = void (A::*)(int);

    // 存储A的实例和成员函数指针
    A* a_instance_;
    AMemberFunc a_func_;

    // 注册回调(绑定A的实例和成员函数)
    void setCallback(A* a, AMemberFunc func) {
        a_instance_ = a;
        a_func_ = func;
    }

    // 触发回调
    void invoke(int data) {
        if (a_instance_ && a_func_) {
            (a_instance_->*a_func_)(data); // 调用成员函数指针
        }
    }
};

int main() {
    A a;
    B b;
    b.setCallback(&a, &A::func); // 注册A的成员函数
    b.invoke(300); // 输出:A::func 被调用,数据: 300
    return 0;
}
优点
  • 直接关联非静态成员函数,无需静态中间层。
  • 性能好,无额外开销。
缺点
  • 语法复杂((a->*func)(...) 调用方式不直观)。
  • 成员函数指针与类强绑定(只能用于特定类的回调),通用性差。

各类方式对比与选择建议

方式 灵活性 易用性 适用场景
函数指针 + 静态成员 需兼容C代码、简单类间通信
std::function + lambda 现代C++主流场景,灵活的类间回调
接口(观察者模式) 多对多通信、需要明确接口契约的框架设计
成员函数指针 仅用于固定类之间的回调,且追求极致性能

总结

  • 优先推荐std::function + lambda,兼顾灵活性和简洁性,适合大多数类间回调场景。
  • 框架设计:采用接口(观察者模式),通过抽象定义明确类间交互契约。
  • 兼容性需求:使用函数指针 + 静态成员函数,确保与C代码或旧系统兼容。
  • 极少推荐:直接使用成员函数指针,除非有明确的性能需求且类关系固定。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐