如何在不暴露类详细信息的情况下传递Function指针

时间:2011-01-07 13:09:04

标签: c++

我正在创建一个需要允许用户设置回调函数的库。 该库的界面如下:

// Viewer Class Interface Exposed to user
/////////////////////////////
#include "dataType_1.h"
#include "dataType_2.h"

class Viewer
{
    void SetCallbackFuntion( dataType_1* (Func) (dataType_2* ) );
  private:
    dataType_1* (*CallbackFunction) (dataType_2* );
}

在典型用法中,用户需要在回调中访问dataType_3的对象。 但是,这个对象只有他的程序才知道,如下所示。

// User usage
#include "Viewer.h"
#include "dataType_3.h"

// Global Declaration needed
dataType_3* objectDataType3;

dataType_1* aFunction( dataType_2* a)
{
    // An operation on object of type dataType_3
    objectDataType3->DoSomething();
}

main()
{
    Viewer* myViewer;
    myViewer->SetCallbackFunction( &aFunction ); 
}

我的问题如下: 如何避免为 objectDataType3 使用丑陋的全局变量? (objectDataType3是libraryFoo的一部分,所有其他对象dataType_1,dataType_2& Viewer都是libraryFooBar的一部分)因此我希望它们尽可能保持独立。

7 个答案:

答案 0 :(得分:6)

不要在C ++中使用C.

使用界面来表示您想要通知的事实 如果您希望将类型为dataType_3的对象通知给查看器中发生的事件,那么只需将此类型实现为接口,然后您可以直接向查看器注册该对象以进行通知。

// The interface
// Very close to your function pointer definition.
class Listener
{ 
    public:  virtual dataType_1* notify(dataType_2* param) = 0;
};
// Updated viewer to use the interface defineition rather than a pointer.
// Note: In the old days of C when you registered a callback you normally
//       also registered some data that was passed to the callback 
//       (see pthread_create for example)
class Viewer
{
    // Set (or Add) a listener.
    void SetNotifier(Listener* l)       { listener = l; }
    // Now you can just inform all objects that are listening
    // directly via the interface. (remember to check for NULL listener)
    void NotifyList(dataType_2* data)   { if (listener) { listener->notify(data); }

  private:
    Listener*   listener;
};

int main()
{
    dataType_3  objectDataType3;  // must implement the Listener interface

    Viewer      viewer;
    viewer.SetNotifier(&objectDataType3);
}

答案 1 :(得分:5)

使用Boost.Function:

class Viewer
{
  void SetCallbackFuntion(boost::function<datatype_1* (dataType_2*)> func);
private:
  boost::function<datatype_1* (dataType_2*)> CallbackFunction;
}

然后使用Boost.Bind将成员函数指针与对象一起作为函数传递。

答案 2 :(得分:2)

boost :: / std :: function就是这里的解决方案。如果你有一个lambda编译器,你可以将成员函数绑定到它们,另外还有functor和lambdas。

struct local {
    datatype3* object;
    local(datatype3* ptr)
        : object(ptr) {}
    void operator()() {
        object->func();
    }
};

boost::function<void()> func;
func = local(object);
func(); // calls object->func() by magic.

答案 3 :(得分:2)

如果您不想或不能使用boost,那么这类回调函数的典型模式是您可以在注册回调时传递“用户数据”值(通常声明为void*)。然后将该值传递给回调函数。

然后用法如下:

dataType_1* aFunction( dataType_2* a, void* user_ptr )
{
    // Cast user_ptr to datatype_3
    // We know it works because we passed it during set callback
    datatype_3* objectDataType3 = reinterpret_cast<datatype_3*>(user_ptr);

    // An operation on object of type dataType_3
    objectDataType3->DoSomething();
}

main()
{
    Viewer* myViewer;
    dataType_3 objectDataType3;  // No longer needs to be global

    myViewer->SetCallbackFunction( &aFunction, &objectDataType3 );      
}

另一方面的实现只需要将void*与函数指针一起保存:

class Viewer
{
    void SetCallbackFuntion( dataType_1* (Func) (dataType_2*, void*), void* user_ptr );
private:
    dataType_1* (*CallbackFunction) (dataType_2*, void*);
    void* user_ptr;
}

答案 4 :(得分:1)

这样的事情很简单:

class Callback
{
public:
  virtual operator()()=0;
};

template<class T>
class ClassCallback
{
  T* _classPtr;
  typedef void(T::*fncb)();
  fncb _cbProc;
public:
  ClassCallback(T* classPtr,fncb cbProc):_classPtr(classPtr),_cbProc(cbProc){}
  virtual operator()(){
    _classPtr->*_cbProc();
  }
};

您的Viewer类将进行回调,并使用简单的语法调用它:

class Viewer
{
  void SetCallbackFuntion( Callback* );

  void OnCallCallback(){
    m_cb->operator()();
  }
}

其他一些类会使用ClassCallback模板专门化来向查看器注册回调:

// User usage
#include "Viewer.h"
#include "dataType_3.h"


main()
{
  Viewer* myViewer;
  dataType_3 objectDataType3;
  myViewer->SetCallbackFunction( new ClassCallback<dataType_3>(&objectDataType3,&dataType_3::DoSomething));
}

答案 5 :(得分:0)

你在这里混淆了几个问题,这会让你在答案中产生很多困惑。

我将专注于dataType_3的问题。

你说:

  

我想避免声明或   包括我的库中的dataType_3   它有很大的依赖性。

您需要做的是为dataType_3创建一个接口类,它为dataType_3提供操作 - 足迹 - 而不定义其中的所有内容。你会找到关于如何做到in this article(以及其他地方)的提示。这将允许您轻松地包含一个标题,该标题为dataType_3提供了足迹,而不会引入所有依赖项。 (如果你在公共API中有依赖关系,你可能不得不为所有这些都重用这个技巧。这可能会变得乏味,但这是设计不良的API的代价。)

一旦你有了这个,不要传递一个函数进行回调,而是考虑让你的“回调”成为一个实现已知接口的类。您可以在文献中找到这样做有几个优点,但是对于您的具体示例,还有一个优点。您可以继承该接口与基类中的实例化dataType_3对象完成。这意味着您只需要#include dataType_3接口规范,然后使用“回调”框架为您提供的dataType_3实例。

答案 6 :(得分:0)

如果您可以选择在Viewer上强制使用某种形式的约束,我只需模板化,即

template <typename CallBackType>
class Viewer
{
public:
  void SetCallbackFunctor(CallBackType& callback) { _callee = callback; }

  void OnCallback()
  {
    if (_callee) (*_callee)(...);
  }

private:
  // I like references, but you can use pointers
  boost::optional<CallBackType&> _callee;
};

然后在dataType_3中根据需要实施operator(),以便使用。

int main(void)
{
  dataType_3 objectDataType3;
  // IMHO, I would construct with the objectDataType3, rather than separate method
  // if you did that, you can hold a direct reference rather than pointer or boost::optional!
  Viewer<dataType_3> viewer;
  viewer.SetCallbackFunctor(objectDataType3);
}

不需要其他接口,void*等。