【C++】C++11 包装器function 与 绑定器 bind

目录

    • [一、 std::function:可调用对象的类型擦除与统一包装](#一、 std::function:可调用对象的类型擦除与统一包装)
      • [1. 为什么需要 std::function?](#1. 为什么需要 std::function?)
      • [2. std::function 语法与应用解析](#2. std::function 语法与应用解析)
    • [二、 std::bind:参数绑定与调用适配](#二、 std::bind:参数绑定与调用适配)
      • [1. std::bind 的核心机制](#1. std::bind 的核心机制)
      • [2. std::bind 的语法与占位符](#2. std::bind 的语法与占位符)
    • [三、 利用 bind 实现参数柯里化](#三、 利用 bind 实现参数柯里化)
    • [四、 核心总结](#四、 核心总结)

在 C++11 中,可调用对象(Callable Object)包括:函数指针、仿函数(重载了 operator() 的类对象)、Lambda 表达式以及类的成员函数 。这些类型各异的可调用对象在配合模板使用时,会引发模板实例化膨胀问题 。

为了统一和管理这些不同类型的可调用对象,C++11 引入了 <functional> 头文件中的 std::functionstd::bind


一、 std::function:可调用对象的类型擦除与统一包装

1. 为什么需要 std::function?

当我们将不同的可调用对象作为参数传递给函数模板时,编译器会为每一种实际类型实例化出一份独立的代码 。

cpp 复制代码
template<class F, class T>
T useF(F f, T x) {
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}

如果在实际调用中,普通函数仿函数对象Lambda 表达式 ,编译器会实例化出三份不同的 useF 函数 。这种行为会导致代码膨胀,降低编译效率。

std::function 本质是一个类模板(包装器/适配器) 。它通过类型擦除 技术,能够在统一的接口下存储和调用任何具有相同调用签名的可调用对象。使用 std::function 接收参数,可以强制所有兼容的可调用对象转换为同一种类型,从而避免模板被多次实例化。

2. std::function 语法与应用解析

std::function 的模板参数是函数的签名:std::function<返回值类型(参数类型列表)>

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

// 1. 普通函数
int f(int a, int b) { return a + b; }

// 2. 仿函数
struct Functor {
    int operator() (int a, int b) { return a + b; }
};

// 3. 类,包含静态与非静态成员函数
class Plus {
public:
    static int plusi(int a, int b) { return a + b; }
    double plusd(double a, double b) { return a + b; }
};

int main() {
    // 包装普通可调用对象(函数指针、仿函数、Lambda)
    function<int(int, int)> f1 = f;                   
    function<int(int, int)> f2 = Functor();           
    function<int(int, int)> f3 = [](int a, int b) { return a + b; }; 

    cout << f1(1, 1) << endl;
    cout << f2(1, 1) << endl;
    cout << f3(1, 1) << endl;

    // 包装静态成员函数
    // 静态成员函数属于类,不依赖对象,因此只需指定类域和取地址
    function<int(int, int)> f4 = &Plus::plusi;
    cout << f4(1, 1) << endl;

    // 包装非静态成员函数
    // 深入解析:非静态成员函数的底层第一个参数是隐藏的 this 指针。
    // 因此,在 function 的参数列表中,必须显式地增加一个类对象或类指针类型的参数。
    Plus pd;
    
    // 方式一:传入对象指针(Plus*)
    function<double(Plus*, double, double)> f5 = &Plus::plusd; 
    cout << f5(&pd, 1.1, 1.1) << endl; // 调用时传入对象的地址

    // 方式二:传入对象(Plus),底层通常是通过对象调用成员函数
    function<double(Plus, double, double)> f6 = &Plus::plusd;  
    cout << f6(pd, 1.1, 1.1) << endl;      // 传入具名对象
    cout << f6(Plus(), 1.1, 1.1) << endl;  // 传入匿名对象

    return 0;
}

关键点说明 :包装类的非静态成员函数时,必须使用 &类名::函数名 的严格语法取地址。同时,由于非静态成员函数依赖 this 指针,其实际参数列表比显式参数多一个对象指针/对象参数,这一点在定义 function 类型时必须体现。


二、 std::bind:参数绑定与调用适配

1. std::bind 的核心机制

std::bind 是一个函数模板,它接收一个可调用对象,并生成一个新的可调用对象 。它的核心作用是对现有函数的参数进行重新映射、固定或调整顺序。

从底层来看,std::bind 返回的是一个未指定类型的仿函数对象 。当我们调用这个生成的仿函数时,它会将我们传入的参数与内部绑定的参数进行整合,最终去调用原始的可调用对象。

2. std::bind 的语法与占位符

std::bind 使用 std::placeholders 命名空间中的占位符(如 _1, _2)来表示新生成函数的参数位置 。_1 代表新函数的第一个参数,_2 代表第二个,以此类推 。

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

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:
    double plusd(double a, double b) { return a + b; }
};

int main() {
    // 1. 调整参数顺序
    // _2 代表 sub2 被调用时的第二个参数,_1 代表第一个参数
    // 当执行 sub2(10, 5) 时,参数映射为 Sub(5, 10)
    auto sub2 = bind(Sub, _2, _1);
    cout << sub2(10, 5) << endl; 

    // 2. 调整参数个数(固定参数/偏函数应用)
    // 绑死第一个参数为 100,生成一个只需要一个参数的新函数
    auto sub3 = bind(Sub, 100, _1); 
    cout << sub3(5) << endl; // 映射为 Sub(100, 5)

    auto sub4 = bind(Sub, _1, 100);
    cout << sub4(5) << endl; // 映射为 Sub(5, 100)

    // 对于多参函数,可以任意绑死某几个参数
    auto sub5 = bind(SubX, 100, _1, _2); cout << sub5(5, 1) << endl; // SubX(100, 5, 1)
    auto sub6 = bind(SubX, _1, 100, _2); cout << sub6(5, 1) << endl; // SubX(5, 100, 1)
    auto sub7 = bind(SubX, _1, _2, 100); cout << sub7(5, 1) << endl; // SubX(5, 1, 100)

    // 3. 配合 std::function 绑死类成员函数的 this 指针
    // 将对象 Plus() 作为隐藏的 this 指针绑死在第一个参数位置
    // 这样成员函数就可以像普通函数一样,被包装为只需要两个 double 参数的 function
    function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
    cout << f7(1.1, 1.1) << endl; 

    return 0;
}

关键点说明bind 绑定类对象时(如 Plus()),通常是值拷贝。如果需要引用绑定,需要使用 std::ref 或直接传递对象的指针。使用 bind 将成员函数的 this 参数固定后,极大地简化了其在回调函数、事件机制中的调用接口。


三、 利用 bind 实现参数柯里化

柯里化:将多参数函数转换为一系列单参数函数的技术,使得函数可以逐步接收参数并支持参数复用和函数组合。

std::bind 在工程实践中常用于降低函数的调用维度。例如,将一个通用的计算函数,通过绑定固定参数,特化为特定业务场景的具体函数。

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

int main() {
    // 基础计算模型:计算指定利率、本金、年限下的复利净利润
    auto func1 = [](double rate, double monty, int year)->double {
        double ret = monty;
        for (int i = 0; i < year; i++) {
             ret += ret * rate;
        }
        return ret - monty;
    };

    // 业务场景特化:将基础计算模型包装为单参数的特定理财产品计算器
    // 绑死利率(参数1)和年限(参数3),只保留本金(参数2)作为占位符 _1 接收输入
    function<double(double)> func3_1_5  = bind(func1, 0.015, _1, 3);  // 1.5%利率,3年期
    function<double(double)> func5_1_5  = bind(func1, 0.015, _1, 5);  // 1.5%利率,5年期
    function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10); // 2.5%利率,10年期
    function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30); // 3.5%利率,30年期

    // 调用特化后的函数,逻辑更加清晰,接口更加简洁
    cout << func3_1_5(1000000) << endl;
    cout << func5_1_5(1000000) << endl;
    cout << func10_2_5(1000000) << endl;
    cout << func20_3_5(1000000) << endl;

    return 0;
}

通过这种方式,底层复杂的通用接口被解耦并转化为更高层、更具体的业务接口,这也是函数式编程思维在 C++ 中的具体体现。


四、 核心总结

  1. std::function 的核心在于类型统一 ,解决了模板函数因接收不同可调用类型而导致的实例化膨胀问题 。非静态成员函数在被包装时,由于包含隐藏的 this 指针,必须显式传递对象或指针参数。
  2. std::bind 的核心在于接口适配 ,通过内部生成仿函数并映射参数位置,可以调整函数参数的顺序、固定部分参数,或是抹平类成员函数的对象依赖 。返回的未命名类型通常使用 auto 接收,或转换并赋值给签名匹配的 std::function 对象。
相关推荐
多加点辣也没关系1 小时前
JavaScript|第30章:事件机制
开发语言·javascript
小大宇1 小时前
python milvus 案例
开发语言·python·milvus
小小龙学IT1 小时前
C++ Placement New 与显式析构:手动对象生命周期管理的艺术
c++·windows·mfc
Gauss松鼠会2 小时前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结
小保CPP2 小时前
OpenCV C++车型识别2-形状匹配
c++·人工智能·opencv·计算机视觉
mingo_敏2 小时前
DeepAgents : 后端(Backends)
java·开发语言
mabing9932 小时前
Qt QMessageBox、QDialogButtonBox中英文翻译动态切换
开发语言·qt
Sagittarius_A*2 小时前
【RCELABS】Level 17~18 —— PHP命令执行函数与环境变量注入
开发语言·安全·web安全·靶场·php·rce
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-19 支票面额(C语言实现)
c语言·开发语言·数据结构·算法