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

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


当前回答

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

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

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

其他回答

在c++中并没有明确的回调函数的概念。回调机制通常通过函数指针、函子对象或回调对象实现。程序员必须显式地设计和实现回调功能。

根据反馈进行编辑:

尽管这个答案收到了负面的反馈,但它并没有错。我会试着更好地解释我的观点。

C和c++拥有实现回调函数所需的一切。实现回调函数最常见和最简单的方法是将函数指针作为函数参数传递。

However, callback functions and function pointers are not synonymous. A function pointer is a language mechanism, while a callback function is a semantic concept. Function pointers are not the only way to implement a callback function - you can also use functors and even garden variety virtual functions. What makes a function call a callback is not the mechanism used to identify and call the function, but the context and semantics of the call. Saying something is a callback function implies a greater than normal separation between the calling function and the specific function being called, a looser conceptual coupling between the caller and the callee, with the caller having explicit control over what gets called. It is that fuzzy notion of looser conceptual coupling and caller-driven function selection that makes something a callback function, not the use of a function pointer.

例如,IFormatProvider的. net文档说“GetFormat是一个回调方法”,即使它只是一个普通的接口方法。我认为没有人会认为所有的虚方法调用都是回调函数。使GetFormat成为回调方法的不是它如何传递或调用的机制,而是调用方选择调用哪个对象的GetFormat方法的语义。

一些语言包含带有显式回调语义的特性,通常与事件和事件处理相关。例如,c#的事件类型的语法和语义显式地围绕回调的概念设计。Visual Basic有它的Handles子句,它显式地将一个方法声明为回调函数,同时抽象了委托或函数指针的概念。在这些情况下,回调的语义概念集成到语言本身中。

另一方面,C和c++几乎没有显式地嵌入回调函数的语义概念。机制在那里,集成的语义不在那里。你可以很好地实现回调函数,但是为了获得更复杂的、包含显式回调语义的东西,你必须在c++提供的基础上构建它,比如Qt对它们的信号和插槽所做的事情。

简而言之,c++拥有实现回调所需的一切,通常使用函数指针非常简单。它没有的是语义特定于回调的关键字和特性,比如raise、emit、Handles、event +=等。如果您来自具有这些类型的元素的语言,那么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;
};

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

公认的答案是非常有用和相当全面的。然而,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++回调函数的“现代”方式。