在c++中,何时以及如何使用回调函数?

编辑: 我想看一个简单的例子来写一个回调函数。


当前回答

Boost的signals2允许您以线程安全的方式订阅泛型成员函数(没有模板!)

示例:文档-视图信号可用于实现灵活 文档视图架构。该文档将包含到的信号 每个视图都可以连接。下面的文档类 定义支持多视图的简单文本文档。请注意, 它存储了一个信号,所有视图都将连接到这个信号。

class Document
{
public:
    typedef boost::signals2::signal<void ()>  signal_t;

public:
    Document()
    {}

    /* Connect a slot to the signal which will be emitted whenever
      text is appended to the document. */
    boost::signals2::connection connect(const signal_t::slot_type &subscriber)
    {
        return m_sig.connect(subscriber);
    }

    void append(const char* s)
    {
        m_text += s;
        m_sig();
    }

    const std::string& getText() const
    {
        return m_text;
    }

private:
    signal_t    m_sig;
    std::string m_text;
};

接下来,我们可以开始定义视图。下面的TextView类 提供文档文本的简单视图。

class TextView
{
public:
    TextView(Document& doc): m_document(doc)
    {
        m_connection = m_document.connect(boost::bind(&TextView::refresh, this));
    }

    ~TextView()
    {
        m_connection.disconnect();
    }

    void refresh() const
    {
        std::cout << "TextView: " << m_document.getText() << std::endl;
    }
private:
    Document&               m_document;
    boost::signals2::connection  m_connection;
};

其他回答

回调函数是传递给例程的方法,在某个时刻被传递给它的例程调用。

这对于开发可重用软件非常有用。例如,许多操作系统API(如Windows API)大量使用回调。

例如,如果你想处理文件夹中的文件,你可以用你自己的例程调用一个API函数,并且你的例程在指定的文件夹中对每个文件运行一次。这使得API非常灵活。

回调函数是C标准的一部分,因此也是c++的一部分。但如果您正在使用c++,我建议您使用观察者模式:http://en.wikipedia.org/wiki/Observer_pattern

请参阅上面的定义,其中声明将回调函数传递给其他函数,并在某个时刻调用它。

在c++中,让回调函数调用类方法是可取的。当您这样做时,您可以访问成员数据。如果你使用C语言定义回调函数,你必须将它指向一个静态成员函数。这不是很理想。

Here is how you can use callbacks in C++. Assume 4 files. A pair of .CPP/.H files for each class. Class C1 is the class with a method we want to callback. C2 calls back to C1's method. In this example the callback function takes 1 parameter which I added for the readers sake. The example doesn't show any objects being instantiated and used. One use case for this implementation is when you have one class that reads and stores data into temporary space and another that post processes the data. With a callback function, for every row of data read the callback can then process it. This technique cuts outs the overhead of the temporary space required. It is particularly useful for SQL queries that return a large amount of data which then has to be post-processed.

/////////////////////////////////////////////////////////////////////
// C1 H file

class C1
{
    public:
    C1() {};
    ~C1() {};
    void CALLBACK F1(int i);
};

/////////////////////////////////////////////////////////////////////
// C1 CPP file

void CALLBACK C1::F1(int i)
{
// Do stuff with C1, its methods and data, and even do stuff with the passed in parameter
}

/////////////////////////////////////////////////////////////////////
// C2 H File

class C1; // Forward declaration

class C2
{
    typedef void (CALLBACK C1::* pfnCallBack)(int i);
public:
    C2() {};
    ~C2() {};

    void Fn(C1 * pThat,pfnCallBack pFn);
};

/////////////////////////////////////////////////////////////////////
// C2 CPP File

void C2::Fn(C1 * pThat,pfnCallBack pFn)
{
    // Call a non-static method in C1
    int i = 1;
    (pThat->*pFn)(i);
}

C语言中也有回调的方法:函数指针

// Define a type for the callback signature,
// it is not necessary but makes life easier

// Function pointer called CallbackType that takes a float
// and returns an int
typedef int (*CallbackType)(float);

void DoWork(CallbackType callback)
{
  float variable = 0.0f;
  
  // Do calculations
  
  // Call the callback with the variable, and retrieve the
  // result
  int result = callback(variable);

  // Do something with the result
}

int SomeCallback(float variable)
{
  int result;

  // Interpret variable

  return result;
}

int main(int argc, char ** argv)
{
  // Pass in SomeCallback to the DoWork
  DoWork(&SomeCallback);
}

现在,如果你想将类方法作为回调函数传入,对这些函数指针的声明会有更复杂的声明,例如:

// Declaration:
typedef int (ClassName::*CallbackType)(float);

// This method performs work using an object instance
void DoWorkObject(CallbackType callback)
{
  // Class instance to invoke it through
  ClassName objectInstance;

  // Invocation
  int result = (objectInstance.*callback)(1.0f);
}

//This method performs work using an object pointer
void DoWorkPointer(CallbackType callback)
{
  // Class pointer to invoke it through
  ClassName * pointerInstance;

  // Invocation
  int result = (pointerInstance->*callback)(1.0f);
}

int main(int argc, char ** argv)
{
  // Pass in SomeCallback to the DoWork
  DoWorkObject(&ClassName::Method);
  DoWorkPointer(&ClassName::Method);
}

公认的答案是全面的,但与问题有关,我只是想在这里放一个简单的例子。我有一个很久以前写的代码。我想以有序的方式遍历树(左节点然后根节点然后右节点),每当我到达一个节点,我希望能够调用任意函数,这样它就可以做任何事情。

void inorder_traversal(Node *p, void *out, void (*callback)(Node *in, void *out))
{
    if (p == NULL)
        return;
    inorder_traversal(p->left, out, callback);
    callback(p, out); // call callback function like this.
    inorder_traversal(p->right, out, callback);
}


// Function like bellow can be used in callback of inorder_traversal.
void foo(Node *t, void *out = NULL)
{
    // You can just leave the out variable and working with specific node of tree. like bellow.
    // cout << t->item;
    // Or
    // You can assign value to out variable like below
    // Mention that the type of out is void * so that you must firstly cast it to your proper out.
    *((int *)out) += 1;
}
// This function use inorder_travesal function to count the number of nodes existing in the tree.
void number_nodes(Node *t)
{
    int sum = 0;
    inorder_traversal(t, &sum, foo);
    cout << sum;
}

 int main()
{

    Node *root = NULL;
    // What These functions perform is inserting an integer into a Tree data-structure.
    root = insert_tree(root, 6);
    root = insert_tree(root, 3);
    root = insert_tree(root, 8);
    root = insert_tree(root, 7);
    root = insert_tree(root, 9);
    root = insert_tree(root, 10);
    number_nodes(root);
}