Benjamin

静以修身,俭以养德,非澹薄无以明志,非宁静无以致远。
随笔 - 397, 文章 - 0, 评论 - 196, 引用 - 0
数据加载中……

c++和closure(闭包)

一、定义: 是带有上下文的函数。说白了,就是有状态的函数。必须有上下文才能使用。
函数, 带上了状态, 就变成了闭包了. 什么叫 "带上状态" 呢? 意思是这个闭包有属于自己的变量, 这些个变量的值是创建闭包的时候设置的, 并在调用闭包的时候, 可以访问这些变量.
函数是代码, 状态是一组变量 ,将代码和一组变量捆绑 (bind) , 就形成了闭包 ,内部包含 static 变量的函数, 不是闭包, 因为这个 static 变量不能捆绑. 你不能捆绑不同的 static 变量. 这个在编译的时候已经确定了.
二、c++实现闭包的方法:
1、重载 operator() 例子
class MyFunctor { public: MyFunctor(float f) : round(f) {} int operator()(float f) { return f + round; } private: float round; }; float round = 0.5; MyFunctor f(round);

2、std::bind
int my_func(float f, float round) { return f + round; } float round = 0.5; std::function<int(float,float)> f = my_func;

3、lambda表达式,语法形式如下: [函数对象参数] (操作符重载函数参数) mutable或exception声明 -> 返回值类型 { …函数体…}
[函数对象参数],例如[&,a,b]标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。

函数对象参数有以下形式:
[ ] 空没有使用任何函数对象参数。
[=] 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
[&] 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
[this] 函数体内可以使用Lambda所在类中的成员变量。
[a] 将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
[&a] 将a按引用进行传递。
[a, &b] 将a按值进行传递,b按引用进行传递。
[=,&a, &b] 除a和b按引用进行传递外,其他参数都按值进行传递。
[&, a, b] 除a和b按值进行传递外,其他参数都按引用进行传递。

(操作符重载函数参数),例如(int a,int &b)标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递
mutable与exception声明,例如 mutable throw(),可省略
按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身,如果没有添加mutable,相当于对函数参数的增加了const修饰,无法修改参数)。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)。

示例:
->返回值类型,例如 ->int 表示返回 int类型
标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
{函数体},例如{cout<<“abc”;},不可省略,可以为空

// 无函数对象参数,输出:1 2   {    for_each(vctTemp.begin(), vctTemp.end(), [](int v){ cout << v << endl; });   }
   
// 以值方式传递作用域内所有可见的局部变量(包括this),输出:11 12   {    int a = 10;    for_each(vctTemp.begin(), vctTemp.end(), [=](int v){ cout << v+a << endl; });   }   
 // 以引用方式传递作用域内所有可见的局部变量(包括this),输出:11 13 12   {    int a = 10;    for_each(vctTemp.begin(), vctTemp.end(), [&](int v)mutable{ cout << v+a << endl; a++; });    cout << a << endl;   }   
 // 以值方式传递局部变量a,输出:11 13 10   {    int a = 10;    for_each(vctTemp.begin(), vctTemp.end(), [a](int v)mutable{ cout << v+a << endl; a++; });    cout << a << endl;   }
   
// 以引用方式传递局部变量a,输出:11 13 12   {    int a = 10;    for_each(vctTemp.begin(), vctTemp.end(), [&a](int v){ cout << v+a << endl; a++; });    cout << a << endl;   }   
 // 传递this,输出:21 22   {    for_each(vctTemp.begin(), vctTemp.end(), [this](int v){ cout << v+m_nData << endl; });   }    
// 除b按引用传递外,其他均按值传递,输出:11 12 17   {    int a = 10;    int b = 15;    for_each(vctTemp.begin(), vctTemp.end(), [=, &b](int v){ cout << v+a << endl; b++; });    cout << b << endl;   }

int temp = 10;
vector<int> ivec = {30, -10, -20, 50, 40 ,100, -50};
std::sort(ivec.begin(), ivec.end(), [](const int &x, const int &y) {return abs(x) < abs(y);});
std::for_each(ivec.begin(), ivec.end(), [&](int &x) { x += temp; cout << x << endl;});
三、注意事项:
比较上面三种方式,有一些细节需要注意:

1. closure的状态特指其运行的上下文。 closure将存贮它运行时需要的上下文,从而保证在closure创建时的上下文可以在closure运行时依然有效。

比如round就是closure的上下文。保存上下文的这一特点通常被称作“capture”或者是"bind"。 capture可以自己写,比如MyFuctor f(round); 也可以用boost::bind。

当然最方便的还是让编译器帮你自动完成。编译器将自动识别closure用到的变量,然后创建一个匿名的类,将这个变量保存到匿名类的成员变量中。

C++中有两种capture方式,by value和by reference。写法是[=]和[&]。

需要注意的是,capture by reference是不会修改被capture变量的生命周期的,你要保证被capture的变量在closure运行时是有效的。

这一点不像Java,Java中变量被capture的话,就变成被引用了,从而GC不会回收它。

2. closure的类型是隐藏的,每次创建一个closure,编译器都会创建一个新的类型。

如果你想保存一个clousre时就不是那么直接,因为你不知道它的类型。这时那需要一些模板技巧,可参考boost::function的实现。

简单的方式是直接用std::function来保存。

std::function<int(float)> closure;

closure = [](float f) { return 0.0f };

closure = [](float f) { return 1.0f };

四、闭包(匿名函数)用处,可以是流程更清晰,易于理解,一般不能单独使用,必须有上下文,闭包里处理的是上下文中的一些变量。一般情况下不能单独使用
auto Do=[&]()
{

}
auto nextDo=[=](){
}

posted on 2018-05-27 18:17 Benjamin 阅读(755) 评论(0)  编辑 收藏 引用 所属分类: C/C++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理