closure(闭包)、仿函数、std::function、bind、lambda

一、闭包

    closure(闭包)有很多种定义,一种说法是,闭包是带有上下文的函数。说白了,就是有状态的函数。更直接一些,不就是个类吗?换了个名字而已。

    一个函数, 带上了一个状态, 就变成了闭包了. 什么叫 "带上状态" 呢? 意思是这个闭包有属于自己的变量, 这些个变量的值是创建闭包的时候设置的, 并在调用闭包的时候, 可以访问这些变量
    函数是代码, 状态是一组变量 ,将代码和一组变量捆绑 (bind) , 就形成了闭包 ,内部包含 static 变量的函数, 不是闭包, 因为这个 static 变量不能捆绑。 你不能捆绑不同的 static 变量。这个在编译的时候已经确定了。

    闭包的状态捆绑, 必须发生在运行时


二、仿函数

    仿函数(functor)又称为函数对象(function object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符。默认传入的 this 指针提供了访问成员变量的途径。事实上, lambda 和 bind 的原理都是这个。

class MyFunctor
{
public:
    MyFunctor(float f) : round(f) {}
    int operator()(float f) { return f + round; }
private:
    float round = 0.5;
};
MyFunctor f(round);

    它既能像普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息:

class StringAppend{
    public:
        explicit StringAppend(const string& str) : ss(str){}
        void operator() (const string& str) const{
             cout<<str<<' '<<ss<<endl;
        }
    private:
        const string ss;  
};
StringAppend myFunc("is world");
myFunc("hello");
>>>hellois world

三、std::function

    通过std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象。让我们不再纠结那么多的可调用实体,std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。

#include <functional>
#include <iostream>
using namespace std;

std::function< int(int)> Functional;

// 普通函数
int TestFunc(int a)
{
    return a;
}

// Lambda表达式
auto lambda = [](int a)->int{ return a; };

// 仿函数(functor)
class Functor
{
public:
    int operator()(int a)
    {
        return a;
    }
};

// 1.类成员函数
// 2.类静态函数
class TestClass
{
public:
    int ClassMember(int a) { return a; }
    static int StaticMember(int a) { return a; }
};

int main()
{
    // 普通函数
    Functional = TestFunc;
    int result = Functional(10);
    cout << "普通函数:"<< result << endl;

    // Lambda表达式
    Functional = lambda;
    result = Functional(20);
    cout << "Lambda表达式:"<< result << endl;

    // 仿函数
    Functor testFunctor;
    Functional = testFunctor;
    result = Functional(30);
    cout << "仿函数:"<< result << endl;

    // 类成员函数
    TestClass testObj;
    Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
    result = Functional(40);
    cout << "类成员函数:"<< result << endl;

    // 类静态函数
    Functional = TestClass::StaticMember;
    result = Functional(50);
    cout << "类静态函数:"<< result << endl;

    return 0;
}
    关于可调用实体转换为std::function对象需要遵守以下两条原则:
    1)转换后的std::function对象的参数能转换为可调用实体的参数;
    2)可调用实体的返回值能转换为std::function对象的返回值。

    std::function对象最大的用处就是在实现函数回调,使用者需要注意,它不能被用来检查相等或者不相等,但是可以与NULL或者nullptr进行比较。


四、bind

    bind是这样一种机制,它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体,这种机制在回调函数的使用过程中也颇为有用。C++98中,有两个函数bind1st和bind2nd,它们分别可以用来绑定functor的第一个和第二个参数,它们都是只可以绑定一个参数。各种限制,使得bind1st和bind2nd的可用性大大降低。C++0x中,提供了std::bind,它绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制,由用户指定,这个bind才是真正意义上的绑定,有了它,bind1st和bind2nd就没啥用武之地了,因此C++0x中不推荐使用bind1st和bind2nd了,都是deprecated了。下面我们通过例子,来看看bind的用法:

#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)  
    上面的例子中,bf1是把一个两个参数普通函数的第一个参数绑定为10,生成了一个新的一个参数的可调用实体; bf2是把一个类成员函数绑定了类对象,生成了一个像普通函数一样的新的可调用实体; bf3是把类成员函数绑定了类对象和第二个参数,生成了一个新的std::function对象。看懂了上面的例子,下面我们来说说使用bind需要注意的一些事项:
    (1)bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的。
    (2)对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的。
    (3)bind的返回值是可调用实体,可以直接赋给std::function对象。
    (4)对于绑定的指针、引用类型的参数,使用者需要保证在可调用实体调用之前,这些参数是可用的。
    (5)类的this可以通过对象或者指针来绑定。


五、lambda

    C++11 里提供的 lambda表达式是很好的语法糖,其本质和手写的函数对象没有区别。lambda就是用来实现closure的东西,它的最大用途也是在回调函数,它和前面讲的function和bind有着千丝万缕的关系。

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

    (1)这是最简单的lambda表达式,可以认为用了lambda表达式的find_if和下面使用了functor的find_if是等价的
    (2)这个是有返回值的lambda表达式,返回值的语法如上面所示,通过->写在参数列表的括号后面。返回值在下面的情况下是可以省略的:
     a、 返回值是void的时候。
     b、lambda表达式的body中有return expr,且expr的类型与返回值的一样。
    (3)这个是lambda表达式capture本地局部变量的例子,这里三个小例子,分别是capture时不同的语法,第一个小例子中=表示capture的变量pass-by-value, 第二个小拿出中&表示capture的变量pass-by-reference,第三个小例子是说指定了default的pass-by-value, 但是max_value这个单独pass-by-reference。
    (4)这个是lambda表达式capture类成员变量的例子,这里也有三个小例子。第一个小例子是通过this指针来capture成员变量,第二、三个是通过缺省的方式,只不过第二个是通过pass-by-value的方式,第三个是通过pass-by-reference的。

    分析完了上面的例子,我们来总结一下关于lambda表达式使用时的一些注意事项:
    (1)lambda表达式要使用引用变量,需要遵守下面的原则
     a、 在调用上下文中的局部变量,只有capture了才可以引用(如上面的例子3所示)。
     b、非本地局部变量可以直接引用。
    (2)使用者需要注意,closure(lambda表达式生成的可调用实体)引用的变量(主要是指针和引用),在closure调用完成之前,必须保证可用,这一点和上面bind绑定参数之后生成的可调用实体是一致的。
    (3)关于lambda的用处,就是用来生成closure,而closure也是一种可调用实体,所以可以通过std::function对象来保存生成的closure,也可以直接用auto


六、总结

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


转自:http://blog.csdn.net/augusdi/article/details/11771699

http://www.jellythink.com/archives/771

http://www.cnblogs.com/decade-dnbc66/p/5347088.html

http://www.cnblogs.com/Aion/p/3449756.html

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值