您的位置:首页 > 其它

C++11 笔记 std::function,bind绑定器和lambda

2017-11-06 21:59 561 查看

C++11 笔记 std::function,bind绑定器和lambda

背景

c++将boost库中的function和bind在c++11中终于纳入了标准库的体系。

在传统的c++程序中,事件回调是通过虚函数进行的。在现代c++程序中,事件回调有了新的推荐做法,即std::funciton+std::bind,这种方式的一个明显优势是不必担心对象的生存期。

function模板类和bind模板函数,使用它们可以实现类似函数指针的功能,但却比函数指针更加灵活,特别是函数指向类的非静态成员函数时。

std::function可以绑定到全局函数/类静态成员函数(类静态成员函数与全局函数没有区别),如果要绑定到类的非静态成员函数,则需要使用std::bind。

标准库函数bind()和function()定义于头文件
#include <functional>
中(该头文件还包括许多其他函数对象),用于处理函数及函数参数。

closure(闭包)

在开始之间,首先要讲一个概念,closure(闭包),这个概念是理解lambda的基础。lambda表达式, function对象和bind机制有紧密的关系。下面我们来看看wikipedia上对于计算机领域的closure的定义:

A closure (also lexical closure, function closure or function value) is a function together with a referencing environment for the non-local variables of that function.

上面的大义是说,closure是一个函数和它所引用的非本地变量的上下文环境的集合。从定义我们可以得知,closure可以访问在它定义范围之外的变量,也即上面提到的non-local vriables,这就大大增加了它的功力。关于closure的最重要的应用就是回调函数,这也是为什么这里把function, bind和lambda放在一起讲的主要原因,它们三者在使用回调函数的过程中各显神通。下面就为大家一步步接开这三者的神秘面纱。

std::bind

将函数、成员函数和闭包转成function函数对象

将多元(n>1)函数转成一元函数或者(n-i)元函数。

bind()接受一个函数(或者函数对象,或者任何你可以通过”(…)”符号调用的事物),生成一个其有某一个或多个函数参数被“绑定”或重新组织的函数对象。(译注:顾名思义,bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。)例如:

int f(int, char, double);
auto ff = bind(f, placeholders::_1, 'c', 1.2);    // 绑定f()函数调用的第二个和第三个参数,返回一个新的函数对象为ff,它只带有一个int类型的参数
int x = ff(7);                //  f(7, 'c', 1.2);


参数的绑定通常称为”Currying”(译注:Currying—“烹制咖喱烧菜”,此处意指对函数或函数对象进行加工修饰操作), “_1”是一个占位符对象,用于表示当函数f通过函数ff进行调用时,函数ff的第一个参数在函数f的参数列表中的位置。第一个参数称为”_1”, 第二个参数为”_2”,依此类推。例如:

int f(int, char, double);
auto frev = bind(f, _3, _2, _1);        // 翻转参数顺序
int x = frev(1.2, 'c', 7);            // f(7, 'c', 1.2);


此处,auto关键字节约了我们去推断bind返回的结果类型的工作。

我们无法使用bind()绑定一个重载函数的参数,我们必须显式地指出需要绑定的重载函数的版本:

int g(int);
double g(double);
auto g1 = bind(g, _1);                // 错误:调用哪一个g() ?
auto g2 = bind( (double(*)(double))g, _1);    // 正确,但是相当丑陋


自己写代码示例如下(IDE: vs2013):

#include "stdafx.h"
#include <functional>
#include <iostream>
#include <string>

using namespace std;
class Foo{
public:
void methodA(){
cout << "this is Foo::methodA" << endl;
}
void methodInt(int a){
cout << "this is Foo::methodInt, and paramater a is: " << a << endl;
}
void methodString(const string& str){
cout << "this is Foo::methodString, and paramater str is: " << str << endl;
}
};
class Bar{
public:
int methodA(int a){
cout << "this is Bar::methodA, and parameter a is: " << a << endl;
return a;
}
};

int Method(int x, int y){
cout << "x=" << x << "   " << "y=" << y << endl;
return x + y;
}
int _tmain(int argc, _TCHAR* argv[])
{
auto f0 = bind(Method, 10, placeholders::_1);
f0(20);  //same as Method(10,20)
function<void()> f1;  //无参数,无返回值
Foo foo;
f1 = bind(&Foo::methodA, &foo);
f1();
f1 = bind(&Foo::methodInt, &foo, 42);
f1();
f1 = bind(&Foo::methodString, &foo, "hello world");
f1();

function<int(int)>f2;
Bar bar;
f2 = bind(&Bar::methodA, &bar, placeholders::_1);
auto res = f2(13);
cout << res << endl;

return 0;
}


输出:

x=10   y=20
this is Foo::methodA
this is Foo::methodInt, and paramater a is: 42
this is Foo::methodString, and paramater str is: hello world
this is Bar::methodA, and parameter a is: 13
13
请按任意键继续. . .


bind需要注意的一些事项:

bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的

对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的

bind的返回值是可调用实体,可以直接赋给std::function对象

对于绑定的指针、引用类型的参数,使用者需要保证在可调用实体调用之前,这些参数是可用的

类的this可以通过对象或者指针来绑定

std::function

它是函数、函数对象、函数指针、成员函数和lambda的包装器,可以容纳任何类型的函数对象,函数指针,引用函数,成员函数和lambda的指针。

以统一的方式处理函数、函数对象、函数指针、成员函数和lambda。允许保存和延迟执行函数

函数和成员函数作为function

function是一个拥有任何可以以”(…)”符号进行调用的值的类型。特别地,bind的返回结果可以赋值给function类型。function十分易于使用。(译注:更直观地,可以把function看成是一种表示函数的数据类型,就像函数对象一样。只不过普通的数据类型表示的是数据,function表示的是函数这个抽象概念。)例如:

typedef std::function<float (int x, int y)> f ;// 构造一个函数对象,它能表示的是一个返回值为float,两个参数为int,int的函数
struct int_div {        // 构造一个可以使用"()"进行调用的函数对象类型
float operator() (int x, int y) const { return ((float)x)/y; };
};

void HelloWorld::testing()
{
f f1= int_div();                    // 赋值
auto result3 = f1( 10, 2);
}


成员函数可被看做是带有额外参数的自由函数:

struct int_div {        // 构造一个可以使用"()"进行调用的函数对象类型
float operator() (int x, int y) const { return ((float)x)/y; };
int int_div_fun( int x ){ return x; };
};
typedef std::function<int (int_div*, int)> f_2;

bool HelloWorld::init()
{
f_2 f2 = &int_div::int_div_fun;            // 指向成员函数
int_div int_div_object;
int v = f2(&int_div_object, 5);  // 在对象x上用参数5调用X::foo()
std::function<int (int)> ff = std::bind( f2, &int_div_object, std::placeholders::_1);    // f的第一个参数是&x
v = ff(5);                // 调用x.foo(5)
}


可以用function取代函数指针。因为它可以保存函数延迟执行,所以比较适合作为回调函数,也可以把它看做类似于c#中特殊的委托,只有一个成员的委托。

struct int_div {        // 构造一个可以使用"()"进行调用的函数对象类型
float operator() (int x, int y) const { return ((float)x)/y; };
int int_div_fun( int x ){ return x; };

int_div( std::function<void()>& f ):m_callback(f){};
void Notify()
{
m_callback();
}
std::function<void()> m_callback;
};


function还可以作为函数入参,这样可以在函数外部控制函数的内部行为了,让我们的函数变得更加灵活。

void Foo(int x, std::function<void(int)>& f)
{
if(x%2==0)
f(x);
}

void G(int x)
{
cout<<x<<endl;
}

void H(int x)
{
cout<<x+2<<endl;
}

void TestFoo()
{
auto f = std::bind(G, std::placeholders::_1);
Foo(4, f);

//在Foo函数外面更改f的行为
f = std::bind(H, std::placeholders::_1);
Foo(4, f);
}


lambda

讲完了function和bind, 下面我们来看lambda。有python基础的朋友,相信对于lambda不会陌生。看到这里的朋友,请再回忆一下前面讲的closure的概念,lambda就是用来实现closure的东东。它的最大用途也是在回调函数,它和前面讲的function和bind有着千丝万缕的关系。

在c++中引入lambda的主要目的是,让您能够将类似于函数的表达式用作接受函数指针或函数符的函数的参数。

下面我们先通过例子来看看lambda的庐山真面目:

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

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


上面的例子基本覆盖到了lambda表达的基本用法。我们一个个来分析每个例子(标号与上面代码注释中1,2,3,4一致):

这是最简单的lambda表达式,可以认为用了lambda表达式的find_if和下面使用了functor的find_if是等价的

这个是有返回值的lambda表达式,返回值的语法如上面所示,通过->写在参数列表的括号后面。返回值在下面的情况下是可以省略的:

(1)返回值是void的时候

(2)仅当lambda表达式完全由一条返回语句组成时,自动类型推断才会管用。

这个是lambda表达式capture本地局部变量的例子,这里三个小例子,分别是capture时不同的语法,第一个小例子中=表示capture的变量pass-by-value, 第二个小拿出中&表示capture的变量pass-by-reference,第三个小例子是说指定了default的pass-by-value, 但是max_value这个单独pass-by-reference

这个是lambda表达式capture类成员变量的例子,这里也有三个小例子。第一个小例子是通过this指针来capture成员变量,第二、三个是通过缺省的方式,只不过第二个是通过pass-by-value的方式,第三个是通过pass-by-reference的

分析完了上面的例子,我们来总结一下关于lambda表达式使用时的一些注意事项:

lambda表达式要使用引用变量,需要遵守下面的原则:

a. 在调用上下文中的局部变量,只有capture了才可以引用(如上面的例子3所示)

b. 非本地局部变量可以直接引用

使用者需要注意,closure(lambda表达式生成的可调用实体)引用的变量(主要是指针和引用),在closure调用完成之前,必须保证可用,这一点和上面bind绑定参数之后生成的可调用实体是一致的

关于lambda的用处,就是用来生成closure,而closure也是一种可调用实体,所以可以通过std::function对象来保存生成的closure,也可以直接用auto

通过上面的介绍,我们基本了解了function, bind和lambda的用法,把三者结合起来,C++将会变得非常强大,有点函数式编程的味道了。最后,这里再补充一点,对于用bind来生成function和用lambda表达式来生成function, 通常情况下两种都是ok的,但是在参数多的时候,bind要传入很多的std::placeholders,而且看着没有lambda表达式直观,所以通常建议优先考虑使用lambda表达式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: