C++ 回调函数的实现,以及function,bind,lambda表达式的使用

tbd
https://www.jianshu.com/p/c4c84b073413

传统回调方法

常见的回调函数实现方式。
以下转自:
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;
}
  1. 跟类A一起定义一个纯虚函数组成的A的接口类,并在实现中传入该接口类,使用其方法。
  2. 类B继承A的接口类,并实现A的接口方法。
  3. 类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");

常用用法:

  1. 保存自由函数
void printA(int a)
{
    cout<<a<<endl;
}

std::function<void(int a)> func;
func = printA;
func(2);
  1. 保存lambda表达式
std::function<void()> func_1 = [](){cout<<"hello world"<<endl;};
func_1();
  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);
  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指针。如果已经使用了&或者=就默认添加此选项。

使用场景:

  1. 不知道函数名起啥
  2. 我们在做算法题的时候经常会遇到一个问题,比较两个数的大小,第一个数比第二个数大的时候返回true,反之返回false
  3. 使用auto来接收一个lambda表达式,当然我们也可以直接使用C++11里面的新特性function来接收lambda表达式,两者等价的,因为auto是自动类型转换,所以在某些场合使用起来更方便。
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值