C++中的回调函数

cpx*_*cpx 281 c++ function-pointers callback

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

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

Pix*_*ist 400

注意:大多数答案都涵盖了函数指针,这是在C++中实现"回调"逻辑的一种可能性,但是到目前为止并不是我认为最有利的一种.

什么是回调(?)以及为什么要使用它们(!)

回调是类或函数接受的可调用(参见下文),用于根据该回调自定义当前逻辑.

使用回调的一个原因是编写通用代码,该代码与被调用函数中的逻辑无关,并且可以与不同的回调一起使用.

标准算法库的许多功能都<algorithm>使用回调.例如,该for_each算法对一系列迭代器中的每个项应用一元回调:

template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
  for (; first != last; ++first) {
    f(*first);
  }
  return f;
}
Run Code Online (Sandbox Code Playgroud)

可以通过传递适当的callables来首先递增然后打印矢量,例如:

std::vector<double> v{ 1.0, 2.2, 4.0, 5.5, 7.2 };
double r = 4.0;
std::for_each(v.begin(), v.end(), [&](double & v) { v += r; });
std::for_each(v.begin(), v.end(), [](double v) { std::cout << v << " "; });
Run Code Online (Sandbox Code Playgroud)

打印

5 6.2 8 9.5 11.2
Run Code Online (Sandbox Code Playgroud)

回调的另一个应用是向某些事件的调用者发出通知,这些事件启用了一定量的静态/编译时间灵活性.

就个人而言,我使用一个使用两个不同回调的本地优化库:

  • 如果需要函数值和基于输入值向量的梯度(逻辑回调:函数值确定/梯度推导),则调用第一个回调.
  • 对于每个算法步骤调用第二个回调,并接收关于算法收敛的某些信息(通知回调).

Thus, the library designer is not in charge of deciding what happens with the information that is given to the programmer via the notification callback and he needn't worry about how to actually determine function values because they're provided by the logic callback. Getting those things right is a task due to the library user and keeps the library slim and more generic.

Furthermore, callbacks can enable dynamic runtime behaviour.

Imagine some kind of game engine class which has a function that is fired, each time the users presses a button on his keyboard and a set of functions that control your game behaviour. With callbacks you can (re)decide at runtime which action will be taken.

void player_jump();
void player_crouch();

class game_core
{
    std::array<void(*)(), total_num_keys> actions;
    // 
    void key_pressed(unsigned key_id)
    {
        if(actions[key_id]) actions[key_id]();
    }
    // update keybind from menu
    void update_keybind(unsigned key_id, void(*new_action)())
    {
        actions[key_id] = new_action;
    }
};
Run Code Online (Sandbox Code Playgroud)

这里函数key_pressed使用存储的回调actions来获得按下某个键时所需的行为.如果玩家选择更改跳跃按钮,则引擎可以呼叫

game_core_instance.update_keybind(newly_selected_key, &player_jump);
Run Code Online (Sandbox Code Playgroud)

因此,下一次游戏时按下此按钮后,会改变呼叫的行为key_pressed(调用player_jump).

什么是C++(11)中的可调用对象

请参阅C++概念:在cppreference上可调用以获得更正式的描述.

回调功能可以通过C++(11)中的几种方式实现,因为有几种不同的东西可以调用*:

  • 函数指针(包括指向成员函数的指针)
  • std::function 对象
  • Lambda表达式
  • 绑定表达式
  • 函数对象(具有重载函数调用操作符的类operator())

*注意:指向数据成员的指针也是可调用的,但根本不调用任何函数.

详细编写回调的几种重要方法

  • X.1在此帖子中"编写"回调意味着声明和命名回调类型的语法.
  • X.2"调用"回调是指调用这些对象的语法.
  • X.3"使用"回调是指使用回调将参数传递给函数时的语法.

注意:从C++ 17开始,f(...)可以编写类似的调用,std::invoke(f, ...)它也可以处理指向成员大小写的指针.

1.函数指针

函数指针是回调可以具有的"最简单"(就通用性而言,可读性最差)类型.

Let's have a simple function foo:

int foo (int x) { return 2+x; }
Run Code Online (Sandbox Code Playgroud)

1.1 Writing a function pointer/type notation

A function pointer type has the notation

return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)
Run Code Online (Sandbox Code Playgroud)

where a named function pointer type will look like

return_type (* name) (parameter_type_1, parameter_type_2, parameter_type_3)

// i.e. f_int_t is a type: function pointer taking one int argument, returning int
typedef int (*f_int_t) (int); 

// foo_p is a pointer to function taking int returning int
// initialized by pointer to function foo taking int returning int
int (* foo_p)(int) = &foo; 
// can alternatively be written as 
f_int_t foo_p = &foo;
Run Code Online (Sandbox Code Playgroud)

The using declaration gives us the option to make things a little bit more readable, since the typedef for f_int_t can also be written as:

using f_int_t = int(*)(int);
Run Code Online (Sandbox Code Playgroud)

Where (at least for me) it is clearer that f_int_t is the new type alias and recognition of the function pointer type is also easier

And a declaration of a function using a callback of function pointer type will be:

// foobar having a callback argument named moo of type 
// pointer to function returning int taking int as its argument
int foobar (int x, int (*moo)(int));
// if f_int is the function pointer typedef from above we can also write foobar as:
int foobar (int x, f_int_t moo);
Run Code Online (Sandbox Code Playgroud)

1.2 Callback call notation

The call notation follows the simple function call syntax:

int foobar (int x, int (*moo)(int))
{
    return x + moo(x); // function pointer moo called using argument x
}
// analog
int foobar (int x, f_int_t moo)
{
    return x + moo(x); // function pointer moo called using argument x
}
Run Code Online (Sandbox Code Playgroud)

1.3 Callback use notation and compatible types

A callback function taking a function pointer can be called using function pointers.

Using a function that takes a function pointer callback is rather simple:

 int a = 5;
 int b = foobar(a, foo); // call foobar with pointer to foo as callback
 // can also be
 int b = foobar(a, &foo); // call foobar with pointer to foo as callback
Run Code Online (Sandbox Code Playgroud)

1.4 Example

A function ca be written that doesn't rely on how the callback works:

void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
  for (unsigned i = 0; i < n; ++i)
  {
    v[i] = fp(v[i]);
  }
}
Run Code Online (Sandbox Code Playgroud)

where possible callbacks could be

int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }
Run Code Online (Sandbox Code Playgroud)

used like

int a[5] = {1, 2, 3, 4, 5};
tranform_every_int(&a[0], 5, double_int);
// now a == {2, 4, 6, 8, 10};
tranform_every_int(&a[0], 5, square_int);
// now a == {4, 16, 36, 64, 100};
Run Code Online (Sandbox Code Playgroud)

2. Pointer to member function

A pointer to member function (of some class C) is a special type of (and even more complex) function pointer which requires an object of type C to operate on.

struct C
{
    int y;
    int foo(int x) const { return x+y; }
};
Run Code Online (Sandbox Code Playgroud)

2.1 Writing pointer to member function/type notation

A pointer to member function type for some class T has the notation

// can have more or less parameters
return_type (T::*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to C::foo has the type
int (C::*) (int)
Run Code Online (Sandbox Code Playgroud)

where a named pointer to member function will -in analogy to the function pointer- look like this:

return_type (T::* name) (parameter_type_1, parameter_type_2, parameter_type_3)

// i.e. a type `f_C_int` representing a pointer to member function of `C`
// taking int returning int is:
typedef int (C::* f_C_int_t) (int x); 

// The type of C_foo_p is a pointer to member function of C taking int returning int
// Its value is initialized by a pointer to foo of C
int (C::* C_foo_p)(int) = &C::foo;
// which can also be written using the typedef:
f_C_int_t C_foo_p = &C::foo;
Run Code Online (Sandbox Code Playgroud)

Example: Declaring a function taking a pointer to member function callback as one of its arguments:

// C_foobar having an argument named moo of type pointer to member function of C
// where the callback returns int taking int as its argument
// also needs an object of type c
int C_foobar (int x, C const &c, int (C::*moo)(int));
// can equivalently declared using the typedef above:
int C_foobar (int x, C const &c, f_C_int_t moo);
Run Code Online (Sandbox Code Playgroud)

2.2 Callback call notation

The pointer to member function of C can be invoked, with respect to an object of type C by using member access operations on the dereferenced pointer. Note: Parenthesis required!

int C_foobar (int x, C const &c, int (C::*moo)(int))
{
    return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}
// analog
int C_foobar (int x, C const &c, f_C_int_t moo)
{
    return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}
Run Code Online (Sandbox Code Playgroud)

Note: If a pointer to C is available the syntax is equivalent (where the pointer to C must be dereferenced as well):

int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
    if (!c) return x;
    // function pointer meow called for object *c using argument x
    return x + ((*c).*meow)(x); 
}
// or equivalent:
int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
    if (!c) return x;
    // function pointer meow called for object *c using argument x
    return x + (c->*meow)(x); 
}
Run Code Online (Sandbox Code Playgroud)

2.3 Callback use notation and compatible types

A callback function taking a member function pointer of class T can be called using a member function pointer of class T.

Using a function that takes a pointer to member function callback is -in analogy to function pointers- quite simple as well:

 C my_c{2}; // aggregate initialization
 int a = 5;
 int b = C_foobar(a, my_c, &C::foo); // call C_foobar with pointer to foo as its callback
Run Code Online (Sandbox Code Playgroud)

3. std::function objects (header <functional>)

The std::function class is a polymorphic function wrapper to store, copy or invoke callables.

3.1 Writing a std::function object/type notation

The type of a std::function object storing a callable looks like:

std::function<return_type(parameter_type_1, parameter_type_2, parameter_type_3)>

// i.e. using the above function declaration of foo:
std::function<int(int)> stdf_foo = &foo;
// or C::foo:
std::function<int(const C&, int)> stdf_C_foo = &C::foo;
Run Code Online (Sandbox Code Playgroud)

3.2 Callback call notation

The class std::function has operator() defined which can be used to invoke its target.

int stdf_foobar (int x, std::function<int(int)> moo)
{
    return x + moo(x); // std::function moo called
}
// or 
int stdf_C_foobar (int x, C const &c, std::function<int(C const &, int)> moo)
{
    return x + moo(c, x); // std::function moo called using c and x
}
Run Code Online (Sandbox Code Playgroud)

3.3 Callback use notation and compatible types

The std::function callback is more generic than function pointers or pointer to member function since different types can be passed and implicitly converted into a std::function object.

3.3.1 Function pointers and pointers to member functions

A function pointer

int a = 2;
int b = stdf_foobar(a, &foo);
// b == 6 ( 2 + (2+2) )
Run Code Online (Sandbox Code Playgroud)

or a pointer to member function

int a = 2;
C my_c{7}; // aggregate initialization
int b = stdf_C_foobar(a, c, &C::foo);
// b == 11 == ( 2 + (7+2) )
Run Code Online (Sandbox Code Playgroud)

can be used.

3.3.2 Lambda expressions

An unnamed closure from a lambda expression can be stored in a std::function object:

int a = 2;
int c = 3;
int b = stdf_foobar(a, [c](int x) -> int { return 7+c*x; });
// b == 15 ==  a + (7*c*a) == 2 + (7+3*2)
Run Code Online (Sandbox Code Playgroud)

3.3.3 std::bind expressions

The result of a std::bind expression can be passed. For example by binding parameters to a function pointer call:

int foo_2 (int x, int y) { return 9*x + y; }
using std::placeholders::_1;

int a = 2;
int b = stdf_foobar(a, std::bind(foo_2, _1, 3));
// b == 23 == 2 + ( 9*2 + 3 )
int c = stdf_foobar(a, std::bind(foo_2, 5, _1));
// c == 49 == 2 + ( 9*5 + 2 )
Run Code Online (Sandbox Code Playgroud)

Where also objects can be bound as the object for the invocation of pointer to member functions:

int a = 2;
C const my_c{7}; // aggregate initialization
int b = stdf_foobar(a, std::bind(&C::foo, my_c, _1));
// b == 1 == 2 + ( 2 + 7 )
Run Code Online (Sandbox Code Playgroud)

3.3.4 Function objects

Objects of classes having a proper operator() overload can be stored inside a std::function object, as well.

struct Meow
{
  int y = 0;
  Meow(int y_) : y(y_) {}
  int operator()(int x) { return y * x; }
};
int a = 11;
int b = stdf_foobar(a, Meow{8});
// b == 99 == 11 + ( 8 * 11 )
Run Code Online (Sandbox Code Playgroud)

3.4 Example

Changing the function pointer example to use std::function

void stdf_tranform_every_int(int * v, unsigned n, std::function<int(int)> fp)
{
  for (unsigned i = 0; i < n; ++i)
  {
    v[i] = fp(v[i]);
  }
}
Run Code Online (Sandbox Code Playgroud)

gives a whole lot more utility to that function because (see 3.3) we have more possibilities to use it:

// using function pointer still possible
int a[5] = {1, 2, 3, 4, 5};
stdf_tranform_every_int(&a[0], 5, double_int);
// now a == {2, 4, 6, 8, 10};

// use it without having to write another function by using a lambda
stdf_tranform_every_int(&a[0], 5, [](int x) -> int { return x/2; });
// now a == {1, 2, 3, 4, 5}; again

// use std::bind :
int nine_x_and_y (int x, int y) { return 9*x + y; }
using std::placeholders::_1;
// calls nine_x_and_y for every int in a with y being 4 every time
stdf_tranform_every_int(&a[0], 5, std::bind(nine_x_and_y, _1, 4));
// now a == {13, 22, 31, 40, 49};
Run Code Online (Sandbox Code Playgroud)

4. Templated callback type

Using templates, the code calling the callback can be even more general than using std::function objects.

Note that templates are a compile-time feature and are a design tool for compile-time polymorphism. If runtime dynamic behaviour is to be achieved through callbacks, templates will help but they won't induce runtime dynamics.

4.1 Writing (type notations) and calling templated callbacks

Generalizing i.e. the std_ftransform_every_int code from above even further can be achieved by using templates:

template<class R, class T>
void stdf_transform_every_int_templ(int * v,
  unsigned const n, std::function<R(T)> fp)
{
  for (unsigned i = 0; i < n; ++i)
  {
    v[i] = fp(v[i]);
  }
}
Run Code Online (Sandbox Code Playgroud)

with an even more general (as well as easiest) syntax for a callback type being a plain, to-be-deduced templated argument:

template<class F>
void transform_every_int_templ(int * v, 
  unsigned const n, F f)
{
  std::cout << "transform_every_int_templ<" 
    << type_name<F>() << ">\n";
  for (unsigned i = 0; i < n; ++i)
  {
    v[i] = f(v[i]);
  }
}
Run Code Online (Sandbox Code Playgroud)

Note: The included output prints the type name deduced for templated type F. The implementation of type_name is given at the end of this post.

The most general implementation for the unary transformation of a range is part of the standard library, namely std::transform, which is also templated with respect to the iterated types.

template<class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first,
  UnaryOperation unary_op)
{
  while (first1 != last1) {
    *d_first++ = unary_op(*first1++);
  }
  return d_first;
}
Run Code Online (Sandbox Code Playgroud)

4.2 Examples using templated callbacks and compatible types

The compatible types for the templated std::function callback method stdf_transform_every_int_templ are identical to the above mentioned types (see 3.4).

Using the templated version however, the signature of the used callback may change a little:

// Let
int foo (int x) { return 2+x; }
int muh (int const &x) { return 3+x; }
int & woof (int &x) { x *= 4; return x; }

int a[5] = {1, 2, 3, 4, 5};
stdf_transform_every_int_templ<int,int>(&a[0], 5, &foo);
// a == {3, 4, 5, 6, 7}
stdf_transform_every_int_templ<int, int const &>(&a[0], 5, &muh);
// a == {6, 7, 8, 9, 10}
stdf_transform_every_int_templ<int, int &>(&a[0], 5, &woof);
Run Code Online (Sandbox Code Playgroud)

Note: std_ftransform_every_int (non templated version; see above) does work with foo but not using muh.

// Let
void print_int(int * p, unsigned const n)
{
  bool f{ true };
  for (unsigned i = 0; i < n; ++i)
  {
    std::cout << (f ? "" : " ") << p[i]; 
    f = false;
  }
  std::cout << "\n";
}
Run Code Online (Sandbox Code Playgroud)

The plain templated parameter of transform_every_int_templ can be every possible callable type.

int a[5] = { 1, 2, 3, 4, 5 };
print_int(a, 5);
transform_every_int_templ(&a[0], 5, foo);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, muh);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, woof);
print_int(a, 5);
transform_every_int_templ(&a[0], 5, [](int x) -> int { return x + x + x; });
print_int(a, 5);
transform_every_int_templ(&a[0], 5, Meow{ 4 });
print_int(a, 5);
using std::placeholders::_1;
transform_every_int_templ(&a[0], 5, std::bind(foo_2, _1, 3));
print_int(a, 5);
transform_every_int_templ(&a[0], 5, std::function<int(int)>{&foo});
print_int(a, 5);
Run Code Online (Sandbox Code Playgroud)

The above code prints:

1 2 3 4 5
transform_every_int_templ <int(*)(int)>
3 4 5 6 7
transform_every_int_templ <int(*)(int&)>
6 8 10 12 14
transform_every_int_templ <int& (*)(int&)>
9 11 13 15 17
transform_every_int_templ <main::{lambda(int)#1} >
27 33 39 45 51
transform_every_int_templ <Meow>
108 132 156 180 204
transform_every_int_templ <std::_Bind<int(*(std::_Placeholder<1>, int))(int, int)>>
975 1191 1407 1623 1839
transform_every_int_templ <std::function<int(int)>>
977 1193 1409 1625 1841
Run Code Online (Sandbox Code Playgroud)

type_name implementation used above

#include <type_traits>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>

template <class T>
std::string type_name()
{
  typedef typename std::remove_reference<T>::type TR;
  std::unique_ptr<char, void(*)(void*)> own
    (abi::__cxa_demangle(typeid(TR).name(), nullptr,
    nullptr, nullptr), std::free);
  std::string r = own != nullptr?own.get():typeid(TR).name();
  if (std::is_const<TR>::value)
    r += " const";
  if (std::is_volatile<TR>::value)
    r += " volatile";
  if (std::is_lvalue_reference<T>::value)
    r += " &";
  else if (std::is_rvalue_reference<T>::value)
    r += " &&";
  return r;
}
Run Code Online (Sandbox Code Playgroud)

  • @BogeyJammer:如果您没有注意到:答案分为两部分.1.一个小例子的"回调"的一般解释.2.完整的不同callables列表以及使用回调编写代码的方法.欢迎您不要深入细节或阅读整个答案,但仅仅因为您不想要详细的视图,答案无效或"残酷地复制".主题是"c ++回调".即使第1部分适用于OP,其他人也可能认为第2部分很有用.随意指出第一部分缺少信息或建设性批评而不是-1. (28认同)
  • @BogeyJammer我不是新编程,但我是"现代c ++"的新手.这个答案给了我完整的上下文,我需要推断回调的作用,特别是c ++.OP可能没有要求提供多个例子,但是在SO上,通过一种永无止境的方式来教育一个愚蠢的世界,列举一个问题的所有可能的解决方案.如果它读得太像书,我唯一可以提供的建议是通过阅读[其中一些]来练习一点(http://stackoverflow.com/questions/388242/the-definitive-c-book-guide -and列表). (19认同)
  • 不,这对于有经验的程序员来说就像是文档完整章节的残酷复制/粘贴.它没有回答请求简单示例和回调概念的解释的问题.我自己是初学者,这个答案完全无效. (16认同)
  • 第 1 部分对初学者不够友好且不够清晰。我不能更有建设性地说它没有设法让我学到一些东西。而第 2 部分,没有被要求,淹没了页面,即使你假装它很有用,也没有问题,尽管它通常可以在专门的文档中找到,在那里首先会寻找这样的详细信息。我绝对保留反对票。一票代表个人意见,请接受并尊重。 (2认同)

Ram*_* B. 156

还有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);
}
Run Code Online (Sandbox Code Playgroud)

现在,如果要将类方法作为回调传递,那些对这些函数指针的声明具有更复杂的声明,例如:

//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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这与std :: tr1:function的缺点在于回调是按类进行的; 这使得当执行调用的对象不知道要调用的对象的类时,使用C样式的回调是不切实际的. (3认同)
  • 类方法示例中有错误。调用应该是:(instance.*callback)(1.0f) (2认同)

Kar*_*oor 68

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;
};
Run Code Online (Sandbox Code Playgroud)

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

std::function<> 是编写C++回调的"现代"方式.

  • 我知道这是旧的,但因为我差不多开始这样做而且它最终没有处理我的设置(mingw),如果你使用的是GCC版本<4.x,则不支持此方法.我正在使用的一些依赖项在gcc version> = 4.0.1中没有很多工作就无法编译,所以我坚持使用老式的C风格回调,它们运行得很好. (5认同)
  • 出于兴趣,SM在哪本书中给出了这个例子?干杯:) (3认同)

Ree*_*sey 39

回调函数是传递到一个程序中,并呼吁在由其所传递的例程的某一点的方法.

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

例如,如果您想使用文件夹中的文件 - 您可以使用自己的例程调用API函数,并且您的例程将在指定文件夹中的每个文件中运行一次.这允许API非常灵活.

  • 这个答案真的不是普通程序员告诉他不知道的任何事情.我在学习C++的同时熟悉许多其他语言.回调通常与我无关. (61认同)

Mil*_*kic 14

接受的答案非常有用且非常全面.然而,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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 在上面的所有答案中,这个让我了解了什么是回调以及如何使用它们。谢谢。 (2认同)

Dar*_*ryl 9

C++中没有明确的回调函数概念.回调机制通常通过函数指针,仿函数对象或回调对象来实现.程序员必须明确地设计和实现回调功能.

根据反馈进行修改:

尽管这个答案得到了负面的反馈,但这并没有错.我会尝试更好地解释我来自哪里.

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

但是,回调函数和函数指针不是同义词.函数指针是一种语言机制,而回调函数是一种语义概念.函数指针不是实现回调函数的唯一方法 - 您还可以使用仿函数甚至花园种类的虚函数.使函数调用回调的原因不是用于标识和调用函数的机制,而是调用的上下文和语义.说某事是一个回调函数意味着调用函数和被调用的特定函数之间的分离大于正常,调用者和被调用者之间的概念耦合更松散,调用者可以明确控制被调用的内容.宽松的概念耦合和调用者驱动的函数选择的模糊概念使得某些东西成为回调函数,而不是使用函数指针.

例如,IFormatProvider的.NET文档说"GetFormat是一种回调方法",即使它只是一种普通的接口方法.我认为没有人会认为所有虚方法调用都是回调函数.是什么让GetFormat成为一个回调方法,不是传递或调用它的机制,而是调用者调用哪个对象的GetFormat方法的语义.

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

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

简而言之,C++具有实现回调所需的功能,通常可以非常轻松地使用函数指针.它没有的是关键字和特性,其语义特定于回调,例如raise,emit,Handles,event + =等.如果你来自具有这些类型元素的语言,那么C++中的本机回调支持会感到绝望.


Aud*_*oid 6

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

  • "C标准的一部分,因此也是C++的一部分." 这是一个典型的误解,但仍然存在误解:-) (3认同)

小智 5

请参阅上面的定义,其中指出回调函数被传递给其他函数并在某个时刻被调用。

在 C++ 中,希望回调函数调用类方法。当您执行此操作时,您可以访问会员数据。如果您使用 C 方式定义回调,则必须将其指向静态成员函数。这是不太理想的。

以下是如何在 C++ 中使用回调。假设有 4 个文件。每个类有一对 .CPP/.H 文件。类 C1 是具有我们要回调的方法的类。C2 回调 C1 的方法。在此示例中,回调函数采用 1 个参数,这是我为读者添加的。该示例未显示任何正在实例化和使用的对象。此实现的一个用例是,您有一个类读取数据并将数据存储到临时空间中,而另一个类则对数据进行后处理。使用回调函数,对于读取的每一行数据,回调都可以对其进行处理。该技术减少了所需临时空间的开销。它对于返回大量数据然后必须进行后处理的 SQL 查询特别有用。

/////////////////////////////////////////////////////////////////////
// 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);
}
Run Code Online (Sandbox Code Playgroud)