c++使用回调函数

需求

一些外部库用C写的,并且使用了回调函数。自己需要将使用了回调函数的部分封装在类内。

方法1 - 静态成员函数

通过使用静态成员变量作为类实例的指针,可以在静态成员函数中访问该实例。
这样的设计模式通常在单例模式中见到,其中只有一个类的实例存在。
使用这种策略,可以在静态回调函数中访问非静态成员。

代码

#include <iostream>

class MyClass {
public:
    MyClass() {
        MyClassModel = this; // 将当前实例赋值给静态指针
    }

    void RegisterCallback() {
        ExternalFunction(MyStaticCallback);
    }

    void NonStaticMemberFunction() {
        std::cout << "Non-static member function called!" << std::endl;
    }

    static void MyStaticCallback() {
        if (MyClassModel) {
            MyClassModel->NonStaticMemberFunction();
        }
    }

private:
    static MyClass* MyClassModel; // 静态指针,指向类的实例
};

// 初始化静态成员变量
MyClass* MyClass::MyClassModel = nullptr;

// 模拟一个外部函数,它接受一个回调函数作为参数
void ExternalFunction(void(*callback)()) {
    callback();
}

int main() {
    MyClass obj;
    obj.RegisterCallback();
    return 0;
}

上述代码中,我们使用了一个静态成员函数MyStaticCallback作为回调,并通过静态指针MyClassModel在这个函数中访问非静态成员函数。

不过,有几点需要注意:

  1. 线程安全性:应用是多线程的,这种策略可能不是线程安全的。可能需要考虑加锁机制,以确保只有一个线程可以访问和修改静态指针。

  2. 多实例问题:这种策略最适用于单例模式。如果你的类有多个实例,必须确保只有一个实例修改和使用这个静态指针。否则,你可能会遇到意料之外的行为。

  3. 生命周期管理:静态变量的生命周期是从它被初始化开始到程序结束。确保在不需要它的时候适当地管理这个静态指针,例如,避免在对象被销毁后访问它。

方法2 - 静态成员函数+静态指针

如果使用一个非静态成员函数作为回调,通常的做法是:
为回调使用一个静态成员函数或一个自由函数。将实际需要的非静态成员函数作为该静态函数或自由函数的内部实现。

代码

// MyClass.h
// 假设这是外部库回调函数格式定义
typedef void(*CALLBACK)(uint32_t eventType, void* pContext);

// 假设这是外部库的数据类型
typedef void* Handle;

// 假设这是外部库注册函数
void registerCallback(Handle handle, uint32_t eventType, CALLBACK  callback,  void* context);

class MyClass
{
public:
    void fun();
    
    static void staticHandleEvent(uint32_t eventType, void* context);
    void handleEvent1();
    void handleEvent2();
}


// MyClass.cpp
void MyClass::staticHandleEvent(uint32_t eventType, void* context)
{
    MyClass* self = static_cast<MyClass*>(context);
    switch(eventType)
    {
    case 1: self->handleEvent1(); break;
    case 2: self->handleEvent2(); break;
    default: /* handle unknown events */ break;
    }
};

void MyClass::fun()
{
    Handle handle;
    for(uint32_t eventType = 1; eventType <= 2; ++eventType)
        registerCallback(handle, eventType, MyClass::staticHandleEvent, this);
}

int main()
{
    MyClass myClass;
    myClass.fun();
    return 0;
}

方法1和方法2的优劣

当在C++中使用回调时,非静态成员函数与静态成员函数(或全局函数)之间有明显的差异,这些差异决定了各自的优劣。

  1. 非静态成员函数作为回调

    优点

    • 可直接访问类的非静态成员,这意味着更自然、直接的类内部状态管理。
    • 更符合OOP原则,使得代码组织和理解更简单。

    劣势

    • 由于非静态成员函数有一个隐含的this指针参数,它们不能直接用作期望普通函数指针参数的函数的回调。通常需要特定的语法和额外的包装以使其工作,这可能会增加实现的复杂性。
    • 如果库或API只期望函数指针作为回调,使用非静态成员函数会很麻烦。
  2. 静态成员函数(或全局函数)+ 静态指针

    优点

    • 静态成员函数可以直接作为函数指针传递,简化了与许多库或API的交互。
    • 这种方法在C和C++之间具有更好的互操作性。

    劣势

    • 使用静态指针来访问实例可能导致线程安全问题。
    • 对于多个类实例,管理静态指针会更加复杂,因为你需要确保正确地设置和使用它。
    • 使用静态方法可能会导致类的设计变得更加复杂和不直观,特别是当类的正常行为依赖于其内部状态时。
    • 静态指针的生命周期管理可能会出问题,例如可能会尝试访问已被删除的对象。

结论

选择哪种策略取决于具体情境和需求:

  • 如果你正在与一个外部库或API交互,该库期望一个函数指针作为回调,并且你的类不太可能有多个实例,那么静态方法可能是更好的选择。
  • 如果你的类设计依赖于内部状态,并且你想要更自然的OOP风格,那么使用非静态成员函数并找到一种方法来传递它可能是更好的选择。
  • 如果线程安全是一个关注点,那么使用静态指针方法可能需要额外的锁机制。
  • 在设计的早期,考虑回调的需求和限制可以帮助你避免在后期进行大量的重构。

总的来说,不同的策略有其各自的优点和缺点,选择哪一种取决于具体的应用场景和优先级。

方法3 - 使用std::function与std::bind组合

// MyClass.h

#include <iostream> 
#include <functional> 
#include <map>

// MyClass.h
// 假设这是外部库回调函数格式定义
typedef void(*CALLBACK)(uint32_t eventType, void* pContext);

// 假设这是外部库的数据类型
typedef void* Handle;

// 假设这是外部库注册函数
void registerCallback(Handle handle, uint32_t eventType, CALLBACK callback, void* context);

class MyClass
{
public:
    void fun();
    static void staticHandleEvent(uint32_t eventType, void* context);
    void handleEvent1();
    void handleEvent2();
    void handleEvent(uint32_t eventType);
    
 private:
    static std::map<uint32_t, std::function<void()>> functionMap;
};

// MyClass.cpp
std::map<uint32_t, std::function<void()>> MyClass::functionMap;

void MyClass::staticHandleEvent(uint32_t eventType, void* context) 
{ 
    MyClass* self = static_cast<MyClass*>(context); 
    functionMap[eventType]();
}

void MyClass::fun()
{
    Handle handle;
    for(uint32_t eventType = 1; eventType <= 2; ++eventType)
    {
        autp boundFunction = std::bind(&MyClass::handleEvent, this, eventType);
        functionMap[eventType]  = boundFunction;
        registerCallback(handle, eventType, MyClass::staticHandleEvent, this);
    }
}

void MyClass::handleEvent(uint32_t eventType)
{
    switch (eventType)
    {
    case 1 : handleEvent1(); break;
    case 2: handleEvent2(); break;
    default: /* handle unknown events */ break;
    }
}

int main()
{
    MyClass myClass;
    myClass.fun();
}

方法2与方法3的主要区别:

  1. 回调方式

    • 方法1:直接在静态回调函数中调用成员函数。
    • 方法2:使用一个std::function对象(存储在静态functionMap中)来代表成员函数,并在静态回调函数中调用这个std::function对象。
  2. 存储机制

    • 方法1:不需要存储任何额外的信息。
    • 方法2:需要一个静态std::map来存储每个对象的回调函数。
  3. 扩展性

    • 方法1:如果要为每个对象使用不同的回调函数,则需要修改代码。
    • 方法2:由于使用了std::function,可以很容易地为每个对象指定不同的回调函数。

优劣分析:

方法1的优点

  1. 更简单和直接。
  2. 没有额外的存储开销。

方法1的缺点

  1. 若需要为不同对象使用不同的成员函数作为回调,则扩展性不足。

方法2的优点

  1. 使用std::functionstd::bind提供了更多的灵活性和扩展性。
  2. 可以很容易地为每个对象指定不同的回调函数。
  3. 若需要在未来为某些特定对象指定不同的回调,或者使用不同的参数,这种方法更为方便。

方法2的缺点

  1. 需要一个额外的std::map来存储std::function对象,带来一定的存储和查询开销。

倾向选择:

个人而言,如果单例的话可以选用方法1
如果不需要为每个对象指定不同的回调函数,那么方法2更为简单且直接;
但如果项目中需要更多的灵活性,或预见到未来可能需要对回调进行更复杂的管理和配置,那么方法3可能是更好的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值