在c ++中的回调函数 [英] Callback functions in c++

查看:169
本文介绍了在c ++中的回调函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



EDIT:

查看一个简单的例子来编写一个回调函数。

解决方案

在C ++中实现回调逻辑的一种可能性,但是今天不是我认为最有利的。



什么是回调使用它们(!)



回调是类或函数接受的可调用(见下文),用于自定义当前逻辑取决于该回调。



使用回调的一个原因是写出与被调用函数中的逻辑无关的通用代码,并且可以重用不同的回调。 / p>

标准算法库的许多函数< algorithm> 使用回调。例如, for_each 算法对一系列迭代器中的每个项目应用一元回调:

  template< class InputIt,class UnaryFunction> 
UnaryFunction for_each(InputIt first,InputIt last,UnaryFunction f)
{
for(; first!= last; ++ first){
f
}
return f;
}

它可以用来先递增,然后通过传递适当的可例如:

  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(),[& v](double& v){v + = r;});
std :: for_each(v.begin(),v.end(),[](double v){std :: cout<< v<

打印

  5 6.2 8 9.5 11.2 

回调的另一个应用是通知某些



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




  • 如果需要函数值和基于输入值向量的渐变,则调用第一个回调(逻辑回调:函数值确定/渐变派生)。

  • 第二个回调对于每个算法步骤调用一次,并接收关于算法收敛的某些信息(通知回调)。





请参阅 C ++概念:在cppreference上调用一个更正式的描述。



回调函数可以在C ++中用几种方式实现




  • 函数指针(包括指向成员函数的指针)

  • std :: function 对象

  • Lambda表达式

  • / li>
  • 函数对象(具有重载函数调用操作符的类 operator()



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



详细写入回调的几种重要方式





  • X.2调用回调是指调用这些对象的语法。

  • X.3使用回调意味着使用回调向函数传递参数时的语法。



em>注意:从C ++ 17开始,像 f(...)的调用可以写为 std :: invoke(f, ...),它还处理指向成员案例的指针。



1。函数指针



函数指针是一个回调函数可以拥有的最简单的(就通用性而言,就可读性而言是最差的)类型。



让我们有一个简单的函数 foo

  int foo(int x){return 2 + x; } 



1.1编写函数指针/类型符号



函数指针类型具有符号

  return_type(*)(paramter_type_1,paramter_type_2, paramter_type_3)
// ie一个指向foo的指针类型:
int(*)(int)

其中命名函数指针类型将类似于

  return_type name)(paramter_type_1,paramter_type_2,paramter_type_3)

// ie f_int是一个类型:函数指针,带有一个int参数,返回int
typedef int(* f_int_t)(int);

// foo_p是一个指向函数的指针,int返回int
//指针指向函数初始化foo int返回int
int(* foo_p)(int)=& ; foo;
//可以写成
f_int_t foo_p =& foo;

并且使用函数指针类型的回调函数声明 be:

  // foobar具有名为moo的回调参数
//指向函数的指针作为其参数
int foobar(int x,int(* moo)(int));
//如果f_int是从上面的函数指针typedef,我们也可以写foobar as:
int foobar(int x,f_int_t moo);



1.2回拨呼叫记号



符号遵循简单的函数调用语法:

  int foobar(int x,int(* moo)(int))
{
return x + moo(x); //函数指针moo使用参数x
}
//模拟
int foobar(int x,f_int_t moo)
{
return x + moo ; //函数指针moo使用参数调用x
}



1.3回调使用符号和兼容类型



可以使用函数指针调用函数指针的回调函数。



使用函数函数指针回调很简单:

  int a = 5; 
int b = foobar(a,foo); // call foobar with pointer to foo as callback
//也可以是
int b = foobar(a,& foo); // call foobar with pointer to foo as callback



1.4示例



可以写一个不依赖回调的工作方式的函数:

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

可能的回调可能是

  int double_int(int x){return 2 * x; } 
int square_int(int x){return x * x; }

使用方式类似

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



2。指向成员函数的指针



指向成员函数的指针(某些类 C )是一种特殊类型的甚至更复杂的)函数指针,它需要 C 类型的对象来操作。

  struct C 
{
int y;
int foo(int x)const {return x + y; }
};



2.1写指向成员函数/类型符号的指针



某些类 T 指向成员函数类型的符号具有符号

  //可以有更多或更少的参数
return_type(T :: *)(paramter_type_1,paramter_type_2,paramter_type_3)
//指向C :: foo类型为
int(C :: *)(int)

>成员函数的命名指针将类似于函数指针 - 看起来像这样:

  return_type :: * name)(paramter_type_1,paramter_type_2,paramter_type_3)

//即类型`f_C_int`表示`C'成员函数的指针
// int返回int是:
typedef int(C :: * f_C_int_t)(int x);

// C_foo_p的类型是一个指向成员函数的指针,取int返回int
//它的值是由指向foo的指针初始化的C
int(C :: * C_foo_p)(int)=& C :: foo;
//也可以使用typedef写:
f_C_int_t C_foo_p =& C :: foo;

示例:声明函数将指向成员函数回调的指针的参数:

  // C_foobar具有名为moo的参数,指向成员函数的类型指针C 
//其中回调返回int以int为参数
//还需要一个类型为c
的对象int C_foobar(int x,C const& c,int(C :: * moo) );
//可以使用上面的typedef等效地声明:
int C_foobar(int x,C const& c,f_C_int_t moo);



2.2回调调用符号



C 的对象,可以调用 C 的成员函数取消引用的指针。
注意:需要括号。



  int C_foobar(int x,C const& c,int(C :: * moo)(int))
{
return x +(c。* moo)(x); //函数指针moo调用对象c使用参数x
}
//模拟
int C_foobar(int x,C const& c,f_C_int_t moo)
{
return x +(c。* moo)(x); //函数指针moo调用对象c使用参数x
}

:如果指向 C 的指针可用,语法是等效的(其中 C 的指针也必须解除引用) :

  int C_foobar_2(int x,C const * c,int(C :: * meow) ))
{
if(!c)return x;
//函数指针喵叫对象* c使用参数x
return x +((* c)。* meow)(x);
}
//或等价物:
int C_foobar_2(int x,C const * c,int(C :: * meow)(int))
{
if(!c)return x;
//函数指针喵叫对象* c使用参数x
return x +(c-> * meow)(x);
}



2.3回调使用符号和兼容类型



可以使用 T T 类的成员函数指针的回调函数c $ c>。



使用指向成员函数回调的指针的函数类似于函数指针 - 非常简单:

  C my_c {2}; //聚合初始化
int a = 5;
int b = C_foobar(a,my_c,& C :: foo); //使用指向foo的指针调用C_foobar作为其回调



3。 std :: function objects(header < functional>



std :: function 类是用于存储,复制或调用可调用项的多态函数包装。



3.1编写 std :: function 对象/类型符号



std :: function 存储可调用对象的对象如下:

  std :: function< return_type(paramter_type_1, paramter_type_2,paramter_type_3)> 

//使用上面的foo的函数声明:
std :: function< int(int)> stdf_foo =& foo;
//或C :: foo:
std :: function< int(const C& int)> stdf_C_foo =& C :: foo;



3.2回呼呼叫记号



std :: function 具有 operator()定义,可用于调用其目标。

  int stdf_foobar(int x,std :: function< int(int)> moo)
{
return x + moo (X); // std :: function moo called
}
//或
int stdf_C_foobar(int x,C const& c,std :: function< int(C const& > moo)
{
return x + moo(c,x); // std :: function moo using c and x
}



3.3回调使用符号和兼容类型



std :: function 回调比函数指针或指向成员函数的指针更通用,因为不同类型可以传递和隐式转换为 std :: function 对象。



指针和指向成员函数的指针



函数指针

  int a = 2; 
int b = stdf_foobar(a,& foo);
// b == 6(2 +(2 + 2))

指向成员函数的指针

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



一个未命名的闭包,可以使用。

一个lambda表达式可以存储在 std :: function 对象中:

  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)

3.3.3 std :: bind 表达式



可以传递一个 std :: bind 表达式的结果。例如通过将参数绑定到函数指针调用:

  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)

其中还可以将对象绑定为用于调用成员函数的指针的对象:

  int a = 2; 
C const my_c {7}; //聚合初始化
int b = stdf_foobar(a,std :: bind(& C :: foo,my_c,_1));
// b == 1 == 2 +(2 + 7)

3.3.4函数对象



具有适当 operator()重载的类的对象可以存储在 std :: function 对象中。

  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)



3.4示例



更改函数指针示例以使用 std :: function

  void stdf_tranform_every_int(int * v,unsigned n,std :: function< int(int)> fp)
{ 0; i {
v [i] = fp(v [i]);
}
}

见3.3)我们有更多的可能性使用它:

  //使用函数指针仍然可能
int a [5 ] = {1,2,3,4,5};
stdf_tranform_every_int(& a [0],5,double_int);
// now a == {2,4,6,8,10};

//使用它,而不必使用lambda编写另一个函数
stdf_tranform_every_int(& a [0],5,[](int x) - > int {return x / 2;});
// now a == {1,2,3,4,5};再次

//使用std :: bind:
int nine_x_and_y(int x,int y){return 9 * x + y; }
using std :: placeholders :: _ 1;
//为每个int调用nine_x_and_y,y为4每次
stdf_tranform_every_int(& a [0],5,std :: bind(nine_x_and_y,_1,4));
// now a == {13,22,31,40,49};



4。模板回调类型



使用模板,调用回调的代码可以比使用 std :: function 对象。



4.1写入(类型符号)和调用模板回调



泛化即 std_ftransform_every_int 代码可以通过使用模板实现:

  template< class R,类T> 
void stdf_transform_every_int_templ(int * v,
unsigned const n,std :: function< R(T)> fp)
{
for(unsigned i = ; n; ++ i)
{
v [i] = fp(v [i];
}
}
pre>

使用一个更简单的(也是最简单的)回调类型语法作为一个简单的,待推导的模板参数:

  template< class F> 
void transform_every_int_templ(int * v,
unsigned const n,F f)
{
std :: cout<<transform_every_int_templ<
<< type_name< F>()<<> \\\
;
= 0; i {
v [i] = f(v [i]);
}
}

注意:包含的输出打印了模板类型 F type_name 的实现在本文末尾提供。



一般范围的一般实现是标准库的一部分,即 std :: transform
,它也是相对于迭代类型模板化的。

  template< class InputIt,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;
}



4.2使用模板回调和兼容类型的示例



模板 std :: function 回调方法 stdf_transform_every_int_templ 的兼容类型与



但是,使用模板版本,使用的回调的签名可能会改变一点:

  //让
int foo(int x){return 2 + x; }
int muh(int constant& 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)
模板版本;参见上文)使用 foo 但不使用 muh
b
$ b

  //让
void print_int(int * p,unsigned const n)
{
bool f {true };
for(unsigned i = 0; i {
std :: cout< (f:)<< p [i];
f = false
}
std :: cout<< \\\
;
}

transform_every_int_templ 可以是每个可能的可调用类型。

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

上述代码打印:

  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



type_name 上面使用的实现



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


In c++, when and how do you use a callback function?

EDIT:
I would like to see a simple example to write a callback function.

解决方案

Note: Most of the answers cover function pointers which is one possibility to achieve "callback" logic in C++, but as of today not the most favourable one I think.

What are callbacks(?) and why to use them(!)

A callback is a callable (see further down) accepted by a class or function, used to customize the current logic depending on that callback.

One reason to use callbacks is to write generic code which is independant from the logic in the called function and can be reused with different callbacks.

Many functions of the standard algorithms library <algorithm> use callbacks. For example the for_each algorithm applies an unary callback to every item in a range of iterators:

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

which can be used to first increment and then print a vector by passing appropriate callables for example:

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

which prints

5 6.2 8 9.5 11.2

Another application of callbacks is the notification of callers of certain events.

Personally, I use a local optimization library that uses two different callbacks:

  • The first callback is called if a function value and the gradient based on a vector of input values is required (logic callback: function value determination / gradient derivation).
  • The second callback is called once for each algorithm step and receives certain information about the convergence of the algorithm (notification callback).

What are callables in C++(11)?

See C++ concepts: Callable on cppreference for a more formal description.

Callback functionality can be realized in several ways in C++(11) since several different things turn out to be callable*:

  • Function pointers (including pointers to member functions)
  • std::function objects
  • Lambda expressions
  • Bind expressions
  • Function objects (classes with overloaded function call operator operator())

* Note: Pointer to data members are callable as well but no function is called at all.

Several important ways to write callbacks in detail

  • X.1 "Writing" a callback in this post means the syntax to declare and name the callback type.
  • X.2 "Calling" a callback refers to the syntax to call those objects.
  • X.3 "Using" a callback means the syntax when passing arguments to a function using a callback.

Note: As of C++17, a call like f(...) can be written as std::invoke(f, ...) which also handles the pointer to member case.

1. Function pointers

A function pointer is the 'simplest' (in terms of generality; in terms of readability arguably the worst) type a callback can have.

Let's have a simple function foo:

int foo (int x) { return 2+x; }

1.1 Writing a function pointer / type notation

A function pointer type has the notation

return_type (*)(paramter_type_1, paramter_type_2, paramter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)

where a named function pointer type will look like

return_type (* name) (paramter_type_1, paramter_type_2, paramter_type_3)

// i.e. f_int 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;

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

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
}

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

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

where possible callbacks could be

int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }

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

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

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::*)(paramter_type_1, paramter_type_2, paramter_type_3)
// i.e. a pointer to C::foo has the type
int (C::*) (int)

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

return_type (T::* name) (paramter_type_1, paramter_type_2, paramter_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;

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

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
}

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

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

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(paramter_type_1, paramter_type_2, paramter_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;

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
}

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

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

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)

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 )

Where also objects can be bound as the object for the invokation 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 )

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 )

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

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

4. Templated callback type

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

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

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

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

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

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

The plain templated paramter 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);

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

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

这篇关于在c ++中的回调函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆