传统回调方法
常见的回调函数实现方式。
以下转自:
https://www.cnblogs.com/kanite/p/8299147.html
使用函数指针
// CppTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
class Result;
typedef void (Result::*CallbackPtr)(int);
class MathCallBack
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,Result *caller,CallbackPtr callback)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
(caller->*callback)(result);
}
};
class Result
{
public:
void showResult(int res)
{
printf("result = %d\n",res);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Result reShow;
MathCallBack math;
math.Add(1,3,&reShow,&Result::showResult);
system("pause");
return 0;
}
结果类需要知道数学类的处理结果,主要注意的是C++函数指针的写法与调用,必须以(对象.*函数指针)(参数)的形式调用。所以,传递回调函数时需要传入调用对象。
缺点:这种方法用起来没有优点,直接说缺点,耦合度高,数学类需要直接知道结果类,数学类不能重用,调用方式写起来也是别扭。
使用接口类
// CppTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
class Result;
class IProcessResult
{
public:
virtual void ProcessResult(int result)=0;
};
class MathCallBack
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,IProcessResult *process)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
process->ProcessResult(result);
}
};
class Result:public IProcessResult
{
public:
void ProcessResult(int res)
{
printf("result = %d\n",res);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Result reShow;
MathCallBack math;
math.Add(1,3,&reShow);
system("pause");
return 0;
}
跟类A一起定义一个纯虚函数组成的A的接口类,并在实现中传入该接口类,使用其方法。
类B继承A的接口类,并实现A的接口方法。
类A对象即可传入类B对象,实现对B方法的回调
优点:典型的面向接口编程,即结果类针对结果处理接口编程,不针对具体编程,降低耦合度。
缺点:程序中多了一个接口类,多了一个文件,不要小看多了一个文件,在大型项目工程里,有非常多的类似类之间关系,这样做会多出很多只有一个接口函数的类。
使用模板
// CppTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
template<typename T>
class MathTemplate
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,T callback)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
callback.showResult(result);
}
};
class Result
{
public:
void showResult(int res)
{
printf("result = %d\n",res);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Result reShow;
MathTemplate<Result> math;
math.Add(1,3,reShow);
system("pause");
return 0;
}
优点:两个类耦合度低,数学类不需要知道结果类,结果类因为需要数学类处理,肯定要包括数学类。
缺点:写数学类时,必须要知道结果类有showResult这个方法。
尝试改进写成下面这样的,编译不通过,不知道是不是跟模板推导有关系,还是有其他方法,TBD:
template<class T> void Add2(int a, int b, T callback, void (T::*pf)(int))
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1 + ops2;
callback.pf(result);
}
...
math.Add2<Result>(1, 1, reShow, &Result::showResult);
function、bind、lambda表达式
没有系统学习,用法也有待挖掘,mark一下先。
参考:
function、bind以及lamda表达式总结 ,非常好:
https://www.cnblogs.com/yyxt/p/4253088.html
https://blog.csdn.net/liukang325/article/details/53668046
https://blog.csdn.net/qq_34199383/article/details/80469780
https://www.cnblogs.com/yyxt/p/3987717.html
function
我理解为一个函数对象,是c++11提供的对可调用实体(主要包括函数,函数指针,函数引用,可以隐式转换为函数指定的对象,或者实现了opetator()的对象)的一个包装。
那么作为参数,就可以很方便的实现回调了。
#include < functional>
std::function< size_t (const char*) > print_func;
/// normal function -> std::function object
size_t CPrint(const char*) { ... }
print_func = CPrint;
print_func("hello world"):
/// functor -> std::function object
class CxxPrint
{
public:
size_t operator()(const char*) { ... }
};
CxxPrint p;
print_func = p;
print_func("hello world");
常用用法:
保存自由函数
void printA(int a)
{
cout<<a<<endl;
}
std::function<void(int a)> func;
func = printA;
func(2);
保存lambda表达式
std::function<void()> func_1 = [](){cout<<"hello world"<<endl;};
func_1();
保存成员函数
struct Foo { Foo(int num) : num_(num) {} void print_add(int i) const { cout << num_+i << '\n'; } int num_; }; // 保存成员函数 std::function<void(const Foo&, int)> f_add_display = &Foo::print_add; Foo foo(2); f_add_display(foo, 1);
实现回调
#include "stdafx.h"
#include <stdlib.h>
#include <math.h>
#include <iostream>
#include <functional>
class MathCallBack
{
int ops1,ops2;
int result;
public:
void Add(int a,int b,std::function<void (int)> func)
{
ops1 = abs(a); /* 实际上这个函数可能非常复杂,非常耗时,这样回调更突显作用*/
ops2 = abs(b);
result = ops1+ops2;
func(result);
}
};
int main()
{
MathCallBack math;
int c1 = 0;
math.Add(1, 3, [&c1](int result) -> void {
printf("result = %d\n", result);
c1 = result;
});
printf("c1 = %d\n", c1);
system("pause");
return 0;
}
下面传入的是个lambda表达式,类型和func一样的(或者能自动强转的)都可以传入。lambda表达式还可以以引用方式捕获上下文中的变量,达到回调的效果。
bind
我理解为,把上面说的函数对象(可调用实体),某些参数绑定到已有的变量,产生一个新的函数对象(可调用实体),相当于让之前的调用实体默认关联一些参数。参数默认为值传递(pass-by-value),若要引用传递使用std::ref()和std::cref()(按const引用传递),算function的一个扩展。
#include < functional>
int Func(int x, int y);
auto bf1 = std::bind(Func, 10, std::placeholders::_1);
bf1(20); ///< same as Func(10, 20)
class A
{
public:
int Func(int x, int y);
};
A a;
auto bf2 = std::bind(&A::Func, a, std::placeholders::_1, std::placeholders::_2);
bf2(10, 20); ///< same as a.Func(10, 20)
std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, 100);
bf3(10); ///< same as a.Func(10, 100)
int n1 = 1, n2 = 2, n3 = 3;
std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));
lambda表达式
我理解为跟python中的差不多,可以联系起当前可见的上下文,捕获上下文的变量,并形成一个closure闭包,传给调用者。
vector< int> vec;
/// 1. simple lambda
auto it = std::find_if(vec.begin(), vec.end(), [](int i) { return i > 50; });
class A
{
public:
bool operator(int i) const { return i > 50; }
};
auto it = std::find_if(vec.begin(), vec.end(), A());
/// 2. lambda return syntax
std::function< int(int)> square = [](int i) -> int { return i * i; }
/// 3. lambda expr: capture of local variable
{
int min_val = 10;
int max_val = 1000;
auto it = std::find_if(vec.begin(), vec.end(), [=](int i) {
return i > min_val && i < max_val;
});
auto it = std::find_if(vec.begin(), vec.end(), [&](int i) {
return i > min_val && i < max_val;
});
auto it = std::find_if(vec.begin(), vec.end(), [=, &max_value](int i) {
return i > min_val && i < max_val;
});
}
/// 4. lambda expr: capture of class member
class A
{
public:
void DoSomething();
private:
std::vector<int> m_vec;
int m_min_val;
int m_max_va;
};
/// 4.1 capture member by this
void A::DoSomething()
{
auto it = std::find_if(m_vec.begin(), m_vec.end(), [this](int i){
return i > m_min_val && i < m_max_val; });
}
/// 4.2 capture member by default pass-by-value
void A::DoSomething()
{
auto it = std::find_if(m_vec.begin(), m_vec.end(), [=](int i){
return i > m_min_val && i < m_max_val; });
}
/// 4.3 capture member by default pass-by-reference
void A::DoSomething()
{
auto it = std::find_if(m_vec.begin(), m_vec.end(), [&](int i){
return i > m_min_val && i < m_max_val; });
}
变量捕获方式:
[] 不截取任何变量
[&] 截取外部作用域中所有变量,并作为引用在函数体中使用
[=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
[=, &foo] 截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对foo变量使用引用
[bar] 截取bar变量并且拷贝一份在函数体重使用,同时不截取其他变量
[this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。
使用场景:
不知道函数名起啥
我们在做算法题的时候经常会遇到一个问题,比较两个数的大小,第一个数比第二个数大的时候返回true,反之返回false
使用auto来接收一个lambda表达式,当然我们也可以直接使用C++11里面的新特性function来接收lambda表达式,两者等价的,因为auto是自动类型转换,所以在某些场合使用起来更方便。