协调类、继承和C回调 在我的C++项目中,我选择使用C库。在我渴望拥有一个抽象而简单的设计的过程中,我最终做了一些混乱的事情。我的设计要求的一部分是,我可以轻松地为给定任务支持多个API和库(主要是由于我对跨平台支持的要求)。因此,我选择创建一个抽象基类,它将统一处理给定的库选择

协调类、继承和C回调 在我的C++项目中,我选择使用C库。在我渴望拥有一个抽象而简单的设计的过程中,我最终做了一些混乱的事情。我的设计要求的一部分是,我可以轻松地为给定任务支持多个API和库(主要是由于我对跨平台支持的要求)。因此,我选择创建一个抽象基类,它将统一处理给定的库选择,c++,c,oop,C++,C,Oop,考虑一下我的设计的简化: class BaseClass { public: BaseClass() {} ~BaseClass() {} bool init() { return doInit(); } bool run() { return doWork(); } void shutdown() { destroy(); } private: virtual bool doInit() = 0; virtual bool doWork

考虑一下我的设计的简化:

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库,我需要提供一个与C兼容的回调,其形式为
int(*)(int,int)
,但该库不支持为这些回调提供额外的userdata指针。我更喜欢在类中执行所有这些回调,因为类带有一个state对象

我最后做的是

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),因此我考虑使用singleton设计,但出于这一原因,我无法证明这一选择是正确的


有更好的方法吗?

您可以证明这个选择的合理性:您的理由是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)意味着您可以在堆栈上创建该类的实例(如果您不尝试创建多个实例的话)

我遇到的问题是,因为这是一个C库,我需要提供一个与C兼容的int(*)(int,int)形式的回调,但库不支持为这些回调提供额外的userdata指针


你能详细说明一下吗?基于userdata选择回调类型是否有问题?

c库的外部约束规定,当调用回调时,您没有回调的“拥有者”实例的标识。因此,我认为你的做法是正确的


我建议将callbackDispatch方法声明为类的静态成员,并使类本身成为单例(有许多示例说明如何实现单例)。这将允许您为其他库实现类似的类

Dani抢先回答了这个问题,但另一个想法是,您可以使用一个消息传递系统,在该系统中,回调函数将结果发送到类的所有或部分实例。如果没有一种干净的方法来确定哪个实例应该得到结果,那么就让那些不需要它的实例忽略结果


当然,如果您有很多实例,并且必须遍历整个列表,那么这会带来性能问题。

我认为问题在于,由于您的方法不是静态的,因此很容易在一个不应该有内部状态的函数中产生内部状态,因为文件顶部有一个实例,可以在调用之间传递,这是一件非常糟糕的事情(tm)。至少,正如上面Dani所建议的,无论您从C回调内部调用什么方法,都必须是静态的,这样才能保证回调调用不会留下任何剩余状态

以上假设您在最顶端声明了
静态库支持类*\u inst\u ptr
。作为替代,考虑有一个工厂函数,它将从池中按需创建您的<代码> LibrarySupportClass < /代码>的工作副本。这些副本可以在您使用完后返回到池中并被回收,这样您就不会在每次需要该功能时都到处创建实例


通过这种方式,您可以让对象在一次回调调用期间保持状态,因为在一个明确的点上,您的实例将被释放并获得重用的绿灯。对于多线程环境,您也将处于一个更好的位置,在这种情况下,每个线程都有自己的
LibrarySupportClass
实例。

您的回调能否基于
a
和/或
b
选择一个实例?如果是这样,请在全局/静态映射中注册库支持类,然后让
callbackADispatch()
在映射中查找正确的实例


使用互斥体序列化对映射的访问将是使该线程安全的合理方法,但请注意:如果库在调用回调时持有任何锁,那么您可能需要做一些更聪明的事情来避免死锁,取决于您的锁层次结构。

限制在于C库无法向回调传递指针,我需要回调来访问类的成员。用户数据必须是指针吗?你的回调函数可以选择一个基于“a”或“b”的实例吗?不。没有这样的实例。单例让我害怕:不清楚如何正确地销毁单例,继承只会使事情复杂化。我将再看一看这种方法。将_inst_ptr更改为(boost,或std::tr1,如果有的话)弱_ptr,将单例访问器更改为通过调用实例上的.lock创建的共享_ptr。最后一个来电者