C++中的function, bind 和 lambda

本文深入解析C++0x中lambda表达式、function对象和bind机制,通过实例和注意事项,揭示它们在回调函数中的应用,并强调closure概念的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   本文是C++0x系列的第四篇,主要是内容是C++0x中新增的lambda表达式, function对象和bind机制。之所以把这三块放在一起讲,是因为这三块之间有着非常密切的关系,通过对比学习,加深对这部分内容的理解。在开始之间,首先要讲一个概念,closure(闭包),这个概念是理解lambda的基础。下面我们来看看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放在一起讲的主要原因,它们三者在使用回调函数的过程中各显神通。下面就为大家一步步接开这三者的神秘面纱。

  • 1. function

        我们知道,在C++中,可调用实体主要包括函数,函数指针,函数引用,可以隐式转换为函数指定的对象,或者实现了opetator()的对象(即C++98中的functor)。C++0x中,新增加了一个std::function对象,std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。我们来看几个关于function对象的例子:

    #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");

        在上面的例子中,我们把一个普通的函数和一个functor赋值给了一个std::function对象,然后我们通过该对象来调用。其它的C++中的可调用实体都可以像上面一样来使用。通过std::function的包裹,我们可以像传递普通的对象一样来传递可调用实体,这样就很好解决了类型安全的问题。了解了std::function的基本用法,下面我们来看一些使用过程中的注意事项:

    • (1)关于可调用实体转换为std::function对象需要遵守以下两条原则:
      a. 转换后的std::function对象的参数能转换为可调用实体的参数
      b. 可高用实体的返回值能转换为std::function对象的(这里注意一下,所有的可调用实体的返回值都与返回void的std::function对象的返回值兼容)。
    • (2)std::function对象可以refer to满足(1)中条件的任意可调用实体
    • (3)std::function object最大的用处就是在实现函数回调,使用者需要注意,它不能被用来检查相等或者不相等
  • 2. 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可以通过对象或者指针来绑定
  • 3. lambda

        讲完了function和bind, 下面我们来看lambda。有python基础的朋友,相信对于lambda不会陌生。看到这里的朋友,请再回忆一下前面讲的closure的概念,lambda就是用来实现closure的东东。它的最大用途也是在回调函数,它和前面讲的function和bind有着千丝万缕的关系。下面我们先通过例子来看看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;
            });
    }
     
    /// 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

        通过上面的介绍,我们基本了解了function, bind和lambda的用法,把三者结合起来,C++将会变得非常强大,有点函数式编程的味道了。最后,这里再补充一点,对于用bind来生成function和用lambda表达式来生成function, 通常情况下两种都是ok的,但是在参数多的时候,bind要传入很多的std::placeholders,而且看着没有lambda表达式直观,所以通常建议优先考虑使用lambda表达式。

<think>我们首先需要明确用户的问题:了解C++bind函数function函数的用法及区别。根据提供的引用资料,我们可以整理出以下要点:1.**std::bind**:-是一个函数适配器,用于绑定函数(或可调用对象)的参数,生成一个新的可调用对象。-可以绑定普通函数、函数指针、函数对象(包括lambda表达式)、成员函数指针以及数据成员指针。-通过占位符(如`_1,_2`等)来指定调用时传入的参数位置。-使用示例:绑定一个无参函数(引用[4])、绑定成员函数(引用[5])。2.**std::function**:-是一个通用的多态函数包装器,可以存储、复制调用任何可调用目标(如普通函数、lambda表达式、函数指针、函数对象等)。-在头文件`<functional>`中定义。-使用示例:可以像函数指针一样使用(引用[2][3])。**区别**:-`std::bind`主要用于绑定函数参数,生成一个新的可调用对象,而`std::function`主要用于包装可调用对象,使其类型统一。-`std::bind`返回的是一个未指定类型的函数对象(通常我们使用`auto`或者`std::function`来接收),而`std::function`是一个具体的类模板,需要指定函数类型(如`std::function<void()>`)。-我们可以将`std::bind`生成的对象赋给`std::function`,这样就能以统一的方式处理不同的可调用对象。**使用方法**:1.**std::function的使用**:-声明一个`std::function`对象,指定函数签名(如`std::function<返回类型(参数列表)>`)。-可以将任何可调用对象赋值给它。2.**std::bind的使用**:-基本格式:`std::bind(可调用对象,参数列表...)`-使用占位符(在`std::placeholders`命名空间中)来预留参数位置。**示例代码**:下面给出一些具体的使用示例,以展示两者的用法区别:```cpp#include<iostream>#include<functional>//包含std::bindstd::function//普通函数voidprint(inta,doubleb){std::cout<< "a="<< a<< ",b="<< b<< std::endl;}//带成员函数的示例classFoo{public:voidprint_sum(inta,intb){std::cout<< "sum:"<< a+b<< std::endl;}};intmain(){//1.使用std::function包装普通函数std::function<void(int,double)>func1=print;func1(10,3.14);//调用//2.使用std::bind绑定参数,生成新的可调用对象//将print的第一个参数绑定为100,第二个参数占位(由调用时提供)autobind_func1=std::bind(print,100,std::placeholders::_1);bind_func1(2.718);//相当于调用print(100,2.718)//3.使用std::function包装bind的结果std::function<void(double)>func2=bind_func1;func2(1.414);//调用print(100,1.414)//4.成员函数的绑定Foofoo;//绑定成员函数,第一个参数是对象(这里用指针),后面是参数//注意:成员函数需要传递对象的指针或引用autobind_member=std::bind(&Foo::print_sum,&foo,std::placeholders::_1,std::placeholders::_2);bind_member(3,4);//调用foo.print_sum(3,4)//也可以使用std::function来包装std::function<void(int,int)>func3=bind_member;func3(5,6);//调用foo.print_sum(5,6)return0;}```**注意点**:-在绑定成员函数时,需要传递一个对象实例(指针或引用)作为第一个参数(在引用[5]中提到)。-占位符`_1,_2`等定义在`std::placeholders`命名空间中,使用前需要引入(如`usingnamespacestd::placeholders;`或显式指定)。**区别总结表**:|特性|std::bind|std::function||------------------------|------------------------------------------------|-----------------------------------------------||**用途**|绑定函数参数,生成新的可调用对象|包装任意可调用对象,提供统一的调用接口||**返回类型**|未指定类型的函数对象(通常用auto或function接收)|指定的函数类型(如`std::function<void(int)>`)||**是否支持多态**|否(仅绑定)|是(可以存储多种可调用对象)||**与占位符的配合**|是(使用占位符调整参数顺序)|否(本身不绑定参数)||**成员函数的绑定支持**|是|是(但通常需要配合bindlambda使用)|因此,在实际编程中,`std::bind``std::function`常常一起使用,`bind`用来调整参数生成新的可调用对象,然后用`function`来保存统一管理这些可调用对象。§§相关问题§§1.C++11中lambda表达式与std::bind有何异同?2.在什么场景下应该使用std::function而不是函数指针?3.如何用std::bind绑定一个类的静态成员函数?4.std::bind绑定的参数是值传递还是引用传递?5.如何用std::functionstd::bind实现回调函数?</think> ### C++ 中 `std::bind` `std::function` 的用法与区别 #### 🧩 **核心区别** | **特性** | `std::bind` | `std::function` | |------------------|----------------------------------------|-------------------------------------| | **本质** | 函数适配器 | 函数包装器 | | **主要作用** | 绑定参数,生成新可调用对象 | 封装任意可调用对象 | | **返回值类型** | 未指定类型的函数对象 | 明确的函数类型(如 `std::function<void()>`) | | **参数处理** | 支持占位符(`_1`, `_2`)调整参数顺序 | 直接调用,不处理参数绑 | | **依赖关系** | 常用来生成 `std::function` 的输入 | 可独立使用 | --- #### 📚 **详细用法** ##### 1. `std::function`(通用函数包装器) - **功能**:封装任何可调用实体(普通函数、Lambda、函数对象等) - **声明格式**: `std::function<返回类型(参数类型列表)> 对象名;` - **示例**: ```cpp #include <functional> #include <iostream> void print(int a, double b) { std::cout << a << ", " << b << std::endl; } int main() { // 封装普通函数 std::function<void(int, double)> func1 = print; func1(10, 3.14); // 输出:10, 3.14 // 封装Lambda表达式 std::function<int(int)> func2 = [](int x) { return x * x; }; std::cout << func2(5); // 输出:25 } ``` ##### 2. `std::bind`(参数绑定器) - **功能**:绑定参数或调整参数顺序,生成新可调用对象 - **占位符**:`std::placeholders::_1`, `_2`(需包含头文件 `<functional>`) - **示例**: ```cpp #include <functional> #include <iostream> void print(int a, double b, const std::string& s) { std::cout << a << ", " << b << ", " << s << std::endl; } int main() { // 绑定固定参数 auto bound1 = std::bind(print, 100, 3.14, "Hello"); bound1(); // 输出:100, 3.14, Hello // 使用占位符调整参数 auto bound2 = std::bind(print, std::placeholders::_2, std::placeholders::_1, "Fixed"); bound2(2.718, 50); // 输出:50, 2.718, Fixed(参数顺序互换) } ``` ##### 3. **成员函数绑定** 需绑定对象实例(通过指针或引用): ```cpp class MyClass { public: void print(int x) { std::cout << "Value: " << x << std::endl; } }; int main() { MyClass obj; // 绑定成员函数:&类名::函数名 + 对象指针 + 参数 auto bound_mem = std::bind(&MyClass::print, &obj, std::placeholders::_1); bound_mem(42); // 输出:Value: 42 } ``` --- #### 🔍 **核心配合场景** `std::bind` 常用来生成适配后的可调用对象,再由 `std::function` 统一管理: ```cpp std::function<void(double)> func = std::bind(print, 10, std::placeholders::_1, "Text"); func(0.618); // 调用:print(10, 0.618, "Text") ``` --- #### 💡 **关键差异总结** 1. **角色不同**: - `std::function` 是容器,存储各种可调用实体[^2][^3]。 - `std::bind` 是工具,修改函数参数结构[^1][^4]。 2. **类型明确性**: - `std::function` 需明确签名(如 `void(int)`)[^2]。 - `std::bind` 返回类型由编译器推导(通常用 `auto` 接收)[^5]。 3. **参数灵活性**: - `std::bind` 支持占位符重排/忽略参数,`std::function` 调用时必须参数匹配[^5]。 --- #### 🚀 **实际应用场景** - **`std::function` 适用**: - 回调函数注册(如事件处理器) - 统一管理不同来源的函数对象 - 实现函数表(类似函数指针数组) - **`std::bind` 适用**: - 固定部分参数(如配置值) - 适配参数不兼容的接口 - 将成员函数转为无参调用(搭配占位符) > ⚠️ **注意事项**: > 1. 绑定成员函数时需显式传递对象指针(否则行为未定义)。 > 2. 默认值传递参数,需用 `std::ref`/`std::cref` 传递引用[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值