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

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


当前回答

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);
}

其他回答

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

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

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

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

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);
}

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;
};

公认的答案是非常有用和相当全面的。然而,OP声明

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

从c++ 11开始,你有std::function,所以不需要函数指针和类似的东西:

#include <functional>
#include <string>
#include <iostream>

void print_hashes(std::function<int (const std::string&)> hash_calculator) {
    std::string strings_to_hash[] = {"you", "saved", "my", "day"};
    for(auto s : strings_to_hash)
        std::cout << s << ":" << hash_calculator(s) << std::endl;    
}

int main() {
    print_hashes( [](const std::string& str) {   /** lambda expression */
        int result = 0;
        for (int i = 0; i < str.length(); i++)
            result += pow(31, i) * str.at(i);
        return result;
    });
    return 0;
}

顺便说一下,这个例子在某种程度上是真实的,因为您希望使用哈希函数的不同实现来调用print_hashes函数,为此我提供了一个简单的例子。它接收一个字符串,返回一个int(提供的字符串的哈希值),所有你需要记住的语法部分是std::function<int (const std::string&)>,它将这样的函数描述为将调用它的函数的输入参数。

Scott Meyers举了一个很好的例子:

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter
{
public:
  typedef std::function<int (const GameCharacter&)> HealthCalcFunc;

  explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
  : healthFunc(hcf)
  { }

  int healthValue() const { return healthFunc(*this); }

private:
  HealthCalcFunc healthFunc;
};

我认为这个例子说明了一切。

std::function<>是编写c++回调函数的“现代”方式。