在我的C ++项目中,我选择使用C库。我热衷于拥有一个精心设计和简单的设计,我最终做了一点点努力。我的设计要求的一部分是我可以轻松支持给定任务的多个API和库(主要是因为我对跨平台支持的要求)。因此,我选择创建一个抽象基类,它将统一处理给定的库选择。
考虑我的设计简化:
class BaseClass
{
public:
BaseClass() {}
~BaseClass() {}
bool init() { return doInit(); }
bool run() { return doWork(); }
void shutdown() { destroy(); }
private:
virtual bool doInit() = 0;
virtual bool doWork() = 0;
virtual void destroy() = 0;
};
一个继承自它的类:
class LibrarySupportClass : public BaseClass
{
public:
LibrarySupportClass()
: BaseClass(), state_manager(new SomeOtherClass()) {}
int callbackA(int a, int b);
private:
virtual bool doInit();
virtual bool doWork();
virtual void destroy();
SomeOtherClass* state_manager;
};
// LSC.cpp:
bool LibrarySupportClass::doInit()
{
if (!libraryInit()) return false;
// the issue is that I can't do this:
libraryCallbackA(&LibrarySupportClass::callbackA);
return true;
}
// ... and so on
我遇到的问题是因为这是一个C库,我需要提供int (*)(int, int)
形式的C兼容回调,但该库不支持额外的userdata指针对于这些回调。我会更喜欢在类中执行所有这些回调,因为该类带有一个状态对象。
我最终做的是......
static LibrarySupportClass* _inst_ptr = NULL;
static int callbackADispatch(int a, int b)
{
_inst_ptr->callbackA(a, b);
}
bool LibrarySupportClass::doInit()
{
_inst_ptr = this;
if (!libraryInit()) return false;
// the issue is that I can't do this:
libraryCallbackA(&callbackADispatch);
return true;
}
如果LibrarySupportClass被多次实例化,这显然会做坏事(TM),所以我考虑使用单例设计,但由于这个原因,我无法证明这一选择。
有更好的方法吗?
答案 0 :(得分:3)
你可以证明这个选择:你的理由是C库只支持一个回调实例。
单身人士吓唬我:目前尚不清楚如何正确摧毁一个单身人士,而继承只会使问题复杂化。我将再看看这种方法。
我是这样做的。
<强> LibrarySupportClass.h 强>
class LibrarySupportClass : public BaseClass
{
public:
LibrarySupportClass();
~LibrarySupportClass();
static int static_callbackA(int a, int b);
int callbackA(int a, int b);
private:
//copy and assignment are rivate and not implemented
LibrarySupportClass(const LibrarySupportClass&);
LibrarySupportClass& operator=(const LibrarySupportClass&);
private:
static LibrarySupportClass* singleton_instance;
};
<强> LibrarySupportClass.cpp 强>
LibrarySupportClass* LibrarySupportClass::singleton_instance = 0;
int LibrarySupportClass::static_callbackA(int a, int b)
{
if (!singleton_instance)
{
WHAT? unexpected callback while no instance exists
}
else
{
return singleton_instance->callback(a, b);
}
}
LibrarySupportClass::LibrarySupportClass()
{
if (singleton_instance)
{
WHAT? unexpected creation of a second concurrent instance
throw some kind of exception here
}
singleton_instance = this;
}
LibrarySupportClass::~LibrarySupportClass()
{
singleton_instance = 0;
}
我的观点是,你不需要给它一个规范的“单身”的外部界面(例如,它很难被破坏)。
相反,它只有一个的事实可以是私有实现细节,并由私有实现细节强制执行(例如通过构造函数中的throw语句)...假设应用程序代码已经是这样的它不会尝试创建这个类的多个实例。
拥有这样的API(而不是更规范的'singleton'API)意味着您可以在堆栈中创建此类的实例(如果您不想尝试创建多个)它)。
答案 1 :(得分:3)
c库的外部约束规定,当你的回调被调用时,你没有回调的“拥有”实例的标识。因此,我认为你的方法是正确的。
我建议将callbackDispatch方法声明为类的静态成员,并使类本身成为单例(有很多关于如何实现单例的示例)。这将允许您为其他库实现类似的类。
答案 2 :(得分:1)
Dani打败了我的答案,但另一个想法是你可以有一个消息系统,其中回调函数将结果发送给你班级的全部或部分实例。如果没有一个干净的方法来确定哪个实例应该得到结果,那么就让那些不需要它的实例忽略结果。
当然,如果你有很多实例,那么就会出现性能问题,你必须遍历整个列表。
答案 3 :(得分:1)
我看到的问题是因为你的方法不是静态的,你很容易在一个不应该有一个的函数中最终得到一个内部状态,因为在它上面有一个实例文件的顶部,可以在调用之间进行,这是一个非常糟糕的事情(tm)。至少,正如Dani在上面所建议的那样,你在C回调中调用的任何方法都必须是静态的,这样你就可以保证调用回调不会留下剩余状态。
以上假设您已在最顶层声明static LibrarySupportClass* _inst_ptr
。作为替代方案,请考虑使用工厂功能,该功能将根据池中的LibrarySupportClass
按需创建工作副本。这些副本可以在完成后再返回池中并进行回收,这样每次需要该功能时都不会创建实例。
通过这种方式,您可以让对象在单个回调调用期间保持状态,因为您的实例将被清除,并且可以重新使用绿灯。对于多线程环境,您也处于更好的位置,在这种情况下,每个线程都有自己的LibrarySupportClass
实例。
答案 4 :(得分:0)
我遇到的问题是因为这是一个C库,我需要提供形式为int(*)(int,int)的C兼容回调,但是库不支持这些回调的额外userdata指针
你能详细说明吗?选择基于userdata的回调类型是一个问题吗?
答案 5 :(得分:0)
您的回调可以选择基于a
和/或b
的实例吗?如果是这样,那么在全局/静态地图中注册您的库支持类,然后让callbackADispatch()
在地图中查找正确的实例。
使用互斥锁序列化对地图的访问是一种使这种线程安全的合理方法,但要注意:如果库在调用你的回调时持有任何锁,那么你可能需要做一些更聪明的事情来避免死锁,取决于您的锁定层次结构。