一些外部库用C写的,并且使用了回调函数。自己需要将使用了回调函数的部分封装在类内。
通过使用静态成员变量作为类实例的指针,可以在静态成员函数中访问该实例。
这样的设计模式通常在单例模式中见到,其中只有一个类的实例存在。
使用这种策略,可以在静态回调函数中访问非静态成员。
#include
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
在这个函数中访问非静态成员函数。
不过,有几点需要注意:
线程安全性:应用是多线程的,这种策略可能不是线程安全的。可能需要考虑加锁机制,以确保只有一个线程可以访问和修改静态指针。
多实例问题:这种策略最适用于单例模式。如果你的类有多个实例,必须确保只有一个实例修改和使用这个静态指针。否则,你可能会遇到意料之外的行为。
生命周期管理:静态变量的生命周期是从它被初始化开始到程序结束。确保在不需要它的时候适当地管理这个静态指针,例如,避免在对象被销毁后访问它。
如果使用一个非静态成员函数作为回调,通常的做法是:
为回调使用一个静态成员函数或一个自由函数。将实际需要的非静态成员函数作为该静态函数或自由函数的内部实现。
// 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;
}
当在C++中使用回调时,非静态成员函数与静态成员函数(或全局函数)之间有明显的差异,这些差异决定了各自的优劣。
非静态成员函数作为回调:
优点:
劣势:
this
指针参数,它们不能直接用作期望普通函数指针参数的函数的回调。通常需要特定的语法和额外的包装以使其工作,这可能会增加实现的复杂性。静态成员函数(或全局函数)+ 静态指针:
优点:
劣势:
结论:
选择哪种策略取决于具体情境和需求:
总的来说,不同的策略有其各自的优点和缺点,选择哪一种取决于具体的应用场景和优先级。
// MyClass.h
#include
#include
#include
// 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();
}
回调方式:
std::function
对象(存储在静态functionMap
中)来代表成员函数,并在静态回调函数中调用这个std::function
对象。存储机制:
std::map
来存储每个对象的回调函数。扩展性:
std::function
,可以很容易地为每个对象指定不同的回调函数。方法1的优点:
方法1的缺点:
方法2的优点:
std::function
和std::bind
提供了更多的灵活性和扩展性。方法2的缺点:
std::map
来存储std::function
对象,带来一定的存储和查询开销。个人而言,如果单例的话可以选用方法1;
如果不需要为每个对象指定不同的回调函数,那么方法2更为简单且直接;
但如果项目中需要更多的灵活性,或预见到未来可能需要对回调进行更复杂的管理和配置,那么方法3可能是更好的选择。