C++ 11 新增特性(下)

欢迎来到我的频道 【点击跳转专栏】

码云链接 【点此转跳】

⚠️: 由于C++11特性太多 博主将其分成了 三部分 大家可以根据需要跳转到对应部分查找对应特性:

文章目录

  • [1. lambda](#1. lambda)
    • [1.1 lambda表达式语法](#1.1 lambda表达式语法)
    • [1.2 捕捉列表](#1.2 捕捉列表)
    • [1.3 lambda的应⽤](#1.3 lambda的应⽤)
    • [1.4 lambda的原理(了解)](#1.4 lambda的原理(了解))
  • [2. 包装器](#2. 包装器)
    • [2.1 function](#2.1 function)
      • [2.1.1 逆波兰表达式求值例题的题解优化](#2.1.1 逆波兰表达式求值例题的题解优化)
    • [2.2 bind](#2.2 bind)
      • [2.2.1 std::bind 是什么?](#2.2.1 std::bind 是什么?)
      • [2.2.2 核心语法](#2.2.2 核心语法)
      • [2.2.3 结合代码讲解](#2.2.3 结合代码讲解)
      • [2.2.4. 实战:金融计算中的参数绑定(经典应用场景!)](#2.2.4. 实战:金融计算中的参数绑定(经典应用场景!))
      • [2.2.5 bind传引用(加餐)](#2.2.5 bind传引用(加餐))
      • [2.2.6 bind 底层原理(加餐)](#2.2.6 bind 底层原理(加餐))
  • [3. 智能指针](#3. 智能指针)

1. lambda

1.1 lambda表达式语法

  • lambda 表达式 本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式 语法使用层而言没有类型,所以我们一般是用 auto 或者模板参数定义的对象 去接收 lambda 对象
  • lambda表达式的格式:[capture-list] (parameters) -> return type { function body }
  • [capture-list]:捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据 [] 来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节后文我们再细讲。捕捉列表为空也不能省略。
  • (parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同 () 一起省略。
  • ->return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {function body}:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕捉到的变量,函数体为空也不能省略。
cpp 复制代码
int main()
{
    // ⼀个简单的lambda表达式
    auto add1 = [](int x, int y)->int {return x + y; };
    cout << add1(1, 2) << endl; // 输出: 3
    // 1、捕捉为空也不能省略
    // 2、参数为空可以省略
    // 3、返回值可以省略,可以通过返回对象⾃动推导
    // 4、函数题不能省略
    auto func1 = []
    {
    cout << "hello bit" << endl;// 输出: hello bit
    return 0;
    };
    func1();
    int a = 0, b = 1;
    auto swap1 = [](int& x, int& y)
    {
    int tmp = x;
    x = y;
    y = tmp;
    };
    swap1(a, b);
    cout << a << ":" << b << endl;// 输出: 1:0
    return 0;
    }

1.2 捕捉列表

  • lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。
  • 第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x, y, &z] 表示 x 和 y 值捕捉,z 引用捕捉。
cpp 复制代码
// 只能⽤当前lambda局部域和捕捉的对象和全局对象
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b]
{
// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改
//a++;
b++;
int ret = a + b;
return ret;
};
cout << func1() << endl;// 2
  • 第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写一个 = 表示隐式值捕捉,在捕捉列表写一个 & 表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些变量。
cpp 复制代码
int a = 0, b = 1, c = 2, d = 3;
// 隐式值捕捉
// ⽤了哪些变量就捕捉哪些变量
auto func2 = [=]
{
int ret = a + b + c;
return ret;
};
cout << func2() << endl;// 输出: 3
// 隐式引⽤捕捉
// ⽤了哪些变量就捕捉哪些变量
auto func3 = [&]
{
a++;
c++;
d++;
};
func3();
cout << a <<" "<< b <<" "<< c <<" "<< d <<endl;//输出: 1 1 3 4
  • 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=, &x] 表示其他变量隐式值捕捉,x 引用捕捉;[&, x, y] 表示其他变量引用捕捉,x 和 y 值捕捉。当使用混合捕捉时,第一个元素必须是 &=,并且 & 混合捕捉时,后面的捕捉变量必须是值捕捉,同理 = 混合捕捉时,后面的捕捉变量必须是引用捕捉。
cpp 复制代码
// 混合捕捉1
auto func4 = [&, a, b]
{
//a++;
//b++;
c++;
d++;
return a + b + c + d;
};
func4();
cout << a << " " << b << " " << c << " " << d << endl;

⚠️: 
// auto func4 = [&, a, b, &c] 这么写是错误的!!! 不能隐式引用捕捉后 再写显示引用捕捉!!
// 混合捕捉1
auto func5 = [=, &a, &b]
{
a++;
b++;
/*c++;
d++;*/
return a + b + c + d;
};
⚠️: 
// auto func5 = [=, &a, &b, c] 这么写是错误的!!! 不能隐式值捕捉后 再写显示值捕捉!!
  • lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
cpp 复制代码
int x = 0;
// 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量
auto func1 = []()
{
x++; //此时x 为 1
};
// 局部的静态和全局变量不能捕捉,也不需要捕捉
static int m = 0;
auto func6 = []
{
int ret = x + m;
return ret;// ret此时为 1
};
  • 默认情况下,lambda 捕捉列表是被 const 修饰 的,也就是说传值捕捉的过来的对象不能修改,mutable 加在参数列表的后面可以取消其常量性 ,也就是说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)
类别 语法形式 说明 注意事项 / 示例
1. 显式捕捉 [x, &y] 手动列出要捕捉的变量: • 默认为 值捕捉(如 x) • 加 & 为 引用捕捉(如 &y • 多个变量用逗号分隔 • 未列出的外部变量不可用 • 示例:[a, &b] → a 按值,b 按引用
2. 隐式捕捉 [=][&] 自动捕捉 lambda 中用到的所有外部变量: • [=]:全部 值捕捉 • [&]:全部 引用捕捉 • 未使用的变量不会被捕捉 • 示例: [=]{ return x + y; } → x、y 按值捕捉
3. 混合捕捉 [=, &x] [&, x] 结合隐式与显式: • [=, &x]:默认值捕捉,x 特殊处理为引用 • [&, x]:默认引用捕捉,x 特殊处理为值 • 首元素必须是 =&[=, &x] 合法,但 [=, x] 冗余(x 已被 = 捕捉) • [&, &x] 非法(重复引用) • [=, x] 虽合法但无意义
4. 捕捉限制 --- • 不能捕捉 静态局部变量 和 全局变量 • 但可在 lambda 中直接使用它们(无需捕捉) • 若 lambda 定义在全局作用域,捕捉列表必须为空 [] • 静态/全局变量具有静态存储期,作用域全局可见 • 示例: int global = 10; static int s = 5; auto f = []{ return global + s; }; // 合法,无需捕捉
5. 修改捕捉值 [x](...) mutable { ... } 默认情况下,值捕捉的变量是 const 的,不能修改。加 mutable 可取消 const 限定,允许修改副本;但注意 它修改的只是"形参",对原值不会有影响 mutable 写在参数列表之后 修改的是副本,不影响原变量• 使用 mutable 后,即使无参数,也不能省略 ()• 示例 int x = 1; auto f = [x]() mutable { x = 10; }; // 合法 auto g = [x] mutable { x = 10; }; // ❌ 错误!缺少 ()

1.3 lambda的应⽤

  • 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象 ,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
    函数指针 不是很记得的(毕竟一般这个是C语言用的 C++有了仿函数我们基本抛弃了这种写法 忘记很正常)可以参考 我写的 代码解藕部分

下面是仿函数 与 Lambda 表达式在排序中的应用对比,可以很明显看出二者区别

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

// 商品结构体:包含名称、价格、评价
struct Goods
{
    string _name;     // 商品名称
    double _price;    // 价格
    int _evaluate;    // 用户评价(1~5 分)

    // 构造函数:方便初始化
    Goods(const char* str, double price, int evaluate)
        : _name(str)
        , _price(price)
        , _evaluate(evaluate)
    {}
};

// ✅ 方式一:使用传统仿函数(Functor)------ 定义可调用对象类
// 按价格升序比较的仿函数
struct ComparePriceLess
{
    // 重载函数调用运算符 (),使其行为像函数
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;  // 升序:小的在前
    }
};

// 按价格降序比较的仿函数
struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price > gr._price;  // 降序:大的在前
    }
};

int main()
{
    // 初始化商品列表(C++11 列表初始化)
    vector<Goods> v = {
        { "苹果", 2.1, 5 },
        { "香蕉", 3.0, 4 },
        { "橙子", 2.2, 3 },
        { "菠萝", 1.5, 4 }
    };

    // ────────────────────────────────────────
    // ❌ 传统方式:使用仿函数(需要提前定义结构体)
    // 优点:可复用、类型安全;缺点:代码冗长,需额外定义类
    // ────────────────────────────────────────

    sort(v.begin(), v.end(), ComparePriceLess());      // 按价格升序
    sort(v.begin(), v.end(), ComparePriceGreater());   // 按价格降序

    // ────────────────────────────────────────
    // ✅ 现代 C++ 推荐方式:使用 Lambda 表达式
    // 优势:简洁、就地定义、无需额外命名、灵活修改
    // ────────────────────────────────────────

    // 按价格升序(Lambda 写法)
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price;
    });

    // 按价格降序
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price > g2._price;
    });

    // 按评价升序(轻松切换排序字段!)
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate < g2._evaluate;
    });

    // 按评价降序
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate > g2._evaluate;
    });

    // 💡 重点总结:
    // 1. std::sort 的第三个参数是一个"比较器"(Comparator),
    //    要求是可调用对象(函数指针、仿函数、Lambda 等)。
    // 2. 仿函数(如 ComparePriceLess)本质是重载了 operator() 的类对象。
    // 3. Lambda 是编译器自动生成的匿名仿函数,语法更简洁。
    // 4. 对于简单、一次性的比较逻辑,**优先使用 Lambda**;
    //    对于复杂、需复用或多处使用的逻辑,可考虑仿函数或普通函数。

    return 0;
}
对比项 仿函数(Functor) Lambda 表达式
代码位置 需在外部单独定义结构体 可直接写在 sort 调用处
可读性 名字清晰(如 ComparePriceLess 逻辑内联,直观看到比较内容
复用性 高(可在多处使用) 低(通常只用一次)
灵活性 修改需改类定义 随时调整,无需额外命名
适用场景 复杂逻辑、性能敏感、需模板特化 简单比较、快速原型、教学演示
  • lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不断接触到

1.4 lambda的原理(了解)

  • lambda 的原理和范围for很像,编译后从汇编指令层的角度看 ,压根就没有 lambda 和范围for 这样的东西。范围for底层是迭代器 ,而lambda底层是仿函数对象 ,也就是说我们写了一个 lambda 以后,编译器会生成一个对应的仿函数的类
  • 仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体
  • lambda 的捕捉列表 本质是生成的仿函数类的成员变量 ,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。
cpp 复制代码
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
auto func1 = [] {
cout << "hello world" << endl;
};
func1();
return 0;
}

此时下面代码的汇编层面是这样的:

cpp 复制代码
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 捕捉列表的rate,可以看到作为lambda_1类构造函数的参数传递了,这样要拿去初始化成员变量
复制代码
// 下⾯operator()中才能使⽤
00D8295C lea eax,[rate]
00D8295F push eax
00D82960 lea ecx,[r2]
00D82963 call `main'::`2'::<lambda_1>::<lambda_1> (0D81F80h)
// 函数对象
Rate r1(rate);
00D82968 sub esp,8
00D8296B movsd xmm0,mmword ptr [rate]
00D82970 movsd mmword ptr [esp],xmm0
00D82975 lea ecx,[r1]
00D82978 call Rate::Rate (0D81438h)
r1(10000, 2);
00D8297D push 2
00D8297F sub esp,8
00D82982 movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D8298A movsd mmword ptr [esp],xmm0
00D8298F lea ecx,[r1]
00D82992 call Rate::operator() (0D81212h)
// 汇编层可以看到r2 lambda对象调⽤本质还是调⽤operator(),类型是lambda_1,这个类型名
// 的规则是编译器⾃⼰定制的,保证不同的lambda不冲突
r2(10000, 2);
00D82999 push 2
00D8299B sub esp,8
00D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D829A6 movsd mmword ptr [esp],xmm0
00D829AB lea ecx,[r2]
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h)

2. 包装器

2.1 function

cpp 复制代码
template <class T>
class function; // undefined

template <class Ret, class... Args>
class function<Ret(Args...)>;

  • std::function 是一个类模板,也是一个包装器。std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、lambda、bind 表达式 等,存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标,则称它为空。调用空 std::function 的目标导致抛出 std::bad_function_call 异常。
  • 以上是 function 的原型,他被定义 <functional> 头文件中。
  • 函数指针、仿函数、lambda 等可调用对象的类型各不相同,std::function 的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型,下面的代码样例展示了 std::function 作为 map 的参数,实现字符串和可调用对象的映射表功能。
cpp 复制代码
std::function<返回类型(参数类型1, 参数类型2, ...)> 变量名;

说一千道一万 不如例子实用!! 我将在代码案例里面详细展示 为方便对罩 我将把例子按照对照的方式展示

std::function 是一个通用的"可调用对象包装器"
模板参数 <返回类型(参数类型...) > 指定了调用签名

  1. 包装普通函数
cpp 复制代码
// 1️⃣ 普通函数
int f(int a, int b)
{
    return a + b;
}

 // 包装普通函数
    function<int(int, int)> f1 = f;  // 将普通函数 f 包装到 std::function 中
    cout << "f1(1,1) = " << f1(1, 1) << endl;  // 输出: 2
  1. 包装仿函数
cpp 复制代码
// 2️⃣ 仿函数(Functor):重载了 operator() 的类
struct Functor
{
public:
    int operator()(int a, int b)  // 仿函数通过重载 () 运算符来实现调用
    {
        return a + b;  // 返回两个整数的和
    }
};

// 包装仿函数
    function<int(int, int)> f2 = Functor();  // 将仿函数 Functor 实例化并包装
    cout << "f2(1,1) = " << f2(1, 1) << endl;  // 输出: 2
  1. 包装 Lambda 表达式
cpp 复制代码
// 包装 Lambda 表达式
    function<int(int, int)> f3 = [](int a, int b) { return a + b; };  // 将 Lambda 表达式包装
    cout << "f3(1,1) = " << f3(1, 1) << endl;  // 输出: 2
  1. 包装静态成员函数
cpp 复制代码
// 3️⃣ 普通类,包含静态成员函数和普通成员函数
class Plus
{
public:
    Plus(int n = 10) : _n(n) {}  // 构造函数,默认_n为10

    // 静态成员函数:不依赖对象,没有 this 指针
    static int plusi(int a, int b)
    {
        return a + b;  // 返回两个整数的和
    }

    // 普通成员函数:隐含 this 指针,依赖对象状态(_n)
    double plusd(double a, double b)
    {
        return (a + b) * _n;  // 返回两个浮点数的和乘以_n
    }

// 包装静态成员函数
    // 注意:静态成员函数需要加上类作用域和取地址符 &
    function<int(int, int)> f4 = &Plus::plusi;  // 将静态成员函数 plusi 包装
    cout << "f4(1,1) = " << f4(1, 1) << endl;  // 输出: 2
  1. 包装成员函数
cpp 复制代码
class Plus
{
public:
    Plus(int n = 10) : _n(n) {}  // 构造函数,默认_n为10

    // 静态成员函数:不依赖对象,没有 this 指针
    static int plusi(int a, int b)
    {
        return a + b;  // 返回两个整数的和
    }

    // 普通成员函数:隐含 this 指针,依赖对象状态(_n)
    double plusd(double a, double b)
    {
        return (a + b) * _n;  // 返回两个浮点数的和乘以_n
    }


 // 包装普通成员函数(传入对象指针)
    // 普通成员函数有一个隐含的 this 指针参数,因此 std::function 的签名必须显式包含对象参数
    function<double(Plus*, double, double)> f5 = &Plus::plusd;  // 将成员函数 plusd 包装
    Plus pd;  // 创建 Plus 类的对象
    cout << "f5(&pd, 1.1, 1.1) = " << f5(&pd, 1.1, 1.1) << endl;  // 输出: 22
    // 注释:使用 std::function 包装普通成员函数 Plus::plusd,并传递对象指针进行调用。

    // 包装普通成员函数(传入对象值)
    function<double(Plus, double, double)> f6 = &Plus::plusd;  // 将成员函数 plusd 包装
    cout << "f6(pd, 1.1, 1.1) = " << f6(pd, 1.1, 1.1) << endl;  // 输出: 22
    // 注释:使用 std::function 包装普通成员函数 Plus::plusd,并传递对象值进行调用。

   // 包装普通成员函数(传入右值引用)
    function<double(Plus&&, double, double)> f7 = &Plus::plusd;  // 将成员函数 plusd 包装
    cout << "f7(move(pd), 1.1, 1.1) = " << f7(move(pd), 1.1, 1.1) << endl;  // 输出: 22
    cout << "f7(Plus(), 1.1, 1.1) = " << f7(Plus(), 1.1, 1.1) << endl;  // 使用临时对象,输出: 22
    // 注释:使用 std::function 包装普通成员函数 Plus::plusd,并传递右值引用进行调用。

三者比较

cpp 复制代码
function<double(Plus*, double, double)> f5 = &Plus::plusd;  // 传指针
function<double(Plus, double, double)> f6 = &Plus::plusd;   // 传值
function<double(Plus&&, double, double)> f7 = &Plus::plusd; //传左值
特性 传指针 (Plus*) 传值 (Plus) 传右值引用 (Plus&&)
std::function 签名 double(Plus*, double, double) double(Plus, double, double) double(Plus&&, double, double)
调用方式 f(&obj, a, b) f(obj, a, b) f(std::move(obj), a, b)f(临时对象, a, b)
是否复制/移动对象? ❌ 不复制也不移动 直接使用传入指针 ✅ 拷贝构造 创建 Plus 副本 ✅ 移动构造(若可用) 否则回退到拷贝构造
成员函数作用于哪个对象? 原始对象 副本对象 移动后的临时对象
对象生命周期要求 调用时 obj 必须有效 (悬空指针 = 未定义行为) 无要求 (用的是副本) 无要求 (用的是移动后副本)
内存开销 最低(仅存指针) 高(完整对象拷贝) 中(移动通常比拷贝快,但仍有构造)
能否修改原始对象? ✅ 能(因为操作原对象) ❌ 不能(操作副本) ❌ 不能(操作临时对象)
适用场景 高频调用、大对象、需修改原对象 小对象、需隔离状态 临时对象、支持高效移动的对象

2.1.1 逆波兰表达式求值例题的题解优化

这是 题目链接

我曾经在别的地方写过这道题目的题解,点击转跳,这次是优化它!
这是我们本来的传统解法:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        for(auto& str : tokens)
        {
            if(str == "+"||str=="-"||str=="*"||str=="/")
            {
                //运算符,运算,运算结果入栈
                int right=st.top();
                st.pop();

                int left = st.top();
                st.pop();

                switch(str[0])
                {
                    case '+':
                        st.push(left+right);
                    break;
                    case '-':
                        st.push(left-right);
                    break;
                    case '*':
                        st.push(left*right);
                    break;
                    case '/':
                        st.push(left/right);
                    break;
                    default:
                        break;
                }
            }
            else
            {
                //运算数入栈
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

这是使⽤map映射string和function的⽅式实现

cpp 复制代码
// 使⽤map映射string和function的⽅式实现
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
// function作为map的映射可调⽤对象的类型
map<string, function<int(int, int)>> opFuncMap = {
{"+", [](int x, int y){return x + y;}},
{"-", [](int x, int y){return x - y;}},
{"*", [](int x, int y){return x * y;}},
{"/", [](int x, int y){return x / y;}}
};

for(auto& str : tokens)
{
if(opFuncMap.count(str)) // 操作符
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
int ret = opFuncMap[str](left, right);
st.push(ret);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};

2.2 bind

cpp 复制代码
simple(1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
  • bind 是⼀个函数模板 ,它也是⼀个可调⽤对象的包装器 ,可以把他看做⼀个函数适配器 ,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。
  • bind 也在 <functional> 这个头⽂件中。
  • 调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list);
    • 其中newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
    • arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占位符放到placeholders的⼀个命名空间中。

看不懂 是吧? 我一开始看文档的时候也没看懂 但没事 ! 我给你用正常人能听懂的角度解释一下bind!!

2.2.1 std::bind 是什么?

std::bind 是一个函数适配器(function adapter),它能:

  • 固定某些参数(称为"绑定"或"绑死")
  • 调整参数顺序
  • 将可调用对象(函数、成员函数、Lambda 等)转换为新的可调用对象(仿函数)

它定义在 <functional> 头文件中,常与 std::function、算法、回调等配合使用。

注意: 默认按值(by value)捕获所有绑定的参数。这意味着如果你传入一个对象、变量或容器,bind 会拷贝一份副本存储在其内部生成的仿函数中。(具体看2.2.5 和 2.2.6)


2.2.2 核心语法

cpp 复制代码
auto new_func = bind(原函数, arg1, arg2, ..., _1, _2, ...);
  • _1, _2, _3... 是 占位符(placeholders) ,来自 std::placeholders 命名空间。
    • _1 表示新函数的第一个实参
    • _2 表示第二个实参,依此类推
  • 固定值(如 10, Plus())会被提前绑定,调用时不再需要传入

⚠️ 必须写:

cpp 复制代码
using namespace std::placeholders;  
// 或逐个引入:using std::placeholders::_1;
//           using std::placeholders::_2;
//           using std::placeholders::_3;
//           ...

2.2.3 结合代码讲解

cpp 复制代码
int Sub(int a, int b)
{
return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};

▶ 1. 调整参数顺序

cpp 复制代码
auto newSub1 = bind(Sub, _2, _1);  // 原函数 Sub(a, b) → 现在变成 Sub(b, a)
cout << newSub1(10, 5) << endl;    // 相当于 Sub(5, 10) = (5 - 10)*10 = -50

作用 :把第一个实参当作 b,第二个当作 a,实现参数反转。

📌 注意 :这种用法较少见,Lambda 更直观:[](int x, int y){ return Sub(y, x); }


▶ 2. 固定部分参数(最常用!)

  • 情况 A:固定第二个参数
cpp 复制代码
auto newSub2 = bind(Sub, _1, 10);  // Sub(a, 10)
cout << newSub2(20) << endl;       // Sub(20, 10) = (20-10)*10 = 100
  • 情况 B:固定第一个参数
cpp 复制代码
auto newSub3 = bind(Sub, 5, _1);   // Sub(5, b)
cout << newSub3(20) << endl;       // Sub(5, 20) = (5-20)*10 = -150

这就是"部分应用"(Partial Application):把多参函数变成少参函数!


▶ 3. 处理三参数函数

cpp 复制代码
auto newSub4 = bind(SubX, 5, _1, _2);     // SubX(5, b, c)
auto newSub5 = bind(SubX, _1, 5, _2);     // SubX(a, 5, c)
auto newSub6 = bind(SubX, _1, _2, 5);     // SubX(a, b, 5)

调用:

cpp 复制代码
newSub4(10, 20) → SubX(5, 10, 20) = (5-10-20)*10 = -250

灵活组合固定参数和占位符,生成任意签名的新函数


▶ 4. 绑定成员函数(重要!)

普通成员函数有隐含 this 参数,bind 可以提前绑定对象

cpp 复制代码
// 方式1:用 std::function + 显式传对象(你之前的写法)
function<double(Plus&&, double, double)> f6 = &Plus::plusd;

// 方式2:用 bind 提前绑定对象,生成无对象参数的函数!✅ 推荐
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;  // 在临时 Plus() 对象上调用 plusd(1.1, 1.1)

🔍 关键点

  • &Plus::plusd 是成员函数指针
  • Plus() 是绑定的对象(这里是个临时对象)
  • _1, _2 对应 plusd 的两个参数
  • 结果 f7 是一个 double(double, double) 类型的可调用对象,无需再传对象!

💡 这比直接用 function<double(Plus*, ...)> 更简洁,尤其适合回调场景。


2.2.4. 实战:金融计算中的参数绑定(经典应用场景!)

定义了一个复利计算 Lambda:

cpp 复制代码
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

auto func1 = [](double rate, double money, int year) -> double {
    double ret = money;
    for (int i = 0; i < year; i++)
        ret += ret * rate;
    return ret - money;  // 返回利息
};

现在用 bind 固定利率和年限,生成针对不同产品的专用函数:

cpp 复制代码
// 3年期,年利率1.5%
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);

// 5年期,年利率2.0%
function<double(double)> func5_2_0 = bind(func1, 0.02, _1, 5);

// 调用时只需传入本金!
cout << func3_1_5(1000000) << endl;  // 计算100万存3年1.5%的利息

这就是策略配置化

  • 产品部门定义利率和期限 → 开发用 bind 生成专用函数
  • 业务代码只需传本金,逻辑清晰、不易出错

2.2.5 bind传引用(加餐)

多时候我们希望按引用绑定,比如:避免大对象拷贝允许被绑定的函数修改原始对象绑定不能拷贝的对象(如 std::unique_ptr)。

这时就需要使用 std::refstd::cref

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

void modify(int& x) {
    x *= 2;
}

int main() {
    int a = 10;

    // ❌ 默认按值绑定:无法修改 a
    auto f1 = bind(modify, a);   // 错误!modify 需要 int&,但 a 被拷贝成 int
    // 编译会失败:cannot bind non-const lvalue reference to rvalue

    // ✅ 正确:用 std::ref 按引用绑定
    auto f2 = bind(modify, ref(a));
    f2();  // 调用后 a 被修改
    cout << a << endl;  // 输出: 20

    return 0;
}
函数 作用 适用场景
std::ref(x) 创建 可修改的引用包装 绑定非 const 引用参数(如 T&
std::cref(x) 创建 只读引用包装 绑定 const 引用参数(如 const T&

2.2.6 bind 底层原理(加餐)

  • bind 返回一个未命名的仿函数类对象(类似 Lambda 生成的闭包类型)
  • 该对象内部存储了绑定的参数副本(或引用)
  • 调用时,将占位符 _1, _2 替换为实际传入的参数,然后调用原函数

📌 注意:bind 默认按值捕获 所有绑定的参数。若想按引用,需用 std::ref(obj)
bind vs Lambda

特性 std::bind Lambda
语法简洁性 较复杂(需占位符) 更直观(直接写逻辑)
可读性 中(嵌套 bind 难读)
性能 相近(现代编译器优化好) 相近
适用场景 - 固定参数- 成员函数绑定- 与旧 API 配合 - 大多数新代码- 复杂逻辑- 捕获局部变量

现代 C++ 建议

  • 简单参数绑定 → 优先用 Lambda (如 auto f = [&](int x){ return old_func(x, fixed_val); };
  • 绑定成员函数或与模板配合 → bind 仍有价值

3. 智能指针

内容 太多 博主会单独开一章写 尽请期待!!!

相关推荐
闻缺陷则喜何志丹2 小时前
【数论】P12191 [蓝桥杯 2025 省研究生组] 01 串|普及+
c++·数学·蓝桥杯·数论·洛谷
zhengfei6112 小时前
CVE-2025-55182 的 POC,可在 Next.js 16.0.6 上运行
开发语言·javascript·ecmascript
m0_635647482 小时前
Qt中使用opencv库imread函数读出的图片是空
开发语言·c++·qt·opencv·计算机视觉
少控科技2 小时前
QT新手日记034
开发语言·qt
玄同7652 小时前
MermaidTrace库:让Python运行时“自己画出”时序图
开发语言·人工智能·python·可视化·数据可视化·日志·异常
燃于AC之乐2 小时前
【C++手撕STL】Vector模拟实现:从零到一的容器设计艺术
开发语言·c++·容器·stl·vector·底层·模板编程
进击的小头2 小时前
创建型模式:装饰器模式(C语言实战指南)
c语言·开发语言·装饰器模式
开开心心就好2 小时前
视频伪装软件,.vsec格式批量伪装播放专用
java·linux·开发语言·网络·python·电脑·php
御承扬2 小时前
鸿蒙原生系列之懒加载瀑布流组件
c++·harmonyos·懒加载·鸿蒙ndk ui·瀑布流布局