目录
- 一、 std::function:可调用对象的类型擦除与统一包装
- 1. 为什么需要 std::function?
- 2. std::function 语法与应用解析
- 二、 std::bind:参数绑定与调用适配
- 1. std::bind 的核心机制
- 2. std::bind 的语法与占位符
- 三、 利用 bind 实现参数柯里化
- 四、 核心总结
在 C++11 中,可调用对象(Callable Object)包括:函数指针、仿函数(重载了operator()的类对象)、Lambda 表达式以及类的成员函数 。这些类型各异的可调用对象在配合模板使用时,会引发模板实例化膨胀问题 。
为了统一和管理这些不同类型的可调用对象,C++11 引入了<functional>头文件中的std::function和std::bind。
一、 std::function:可调用对象的类型擦除与统一包装
1. 为什么需要 std::function?
当我们将不同的可调用对象作为参数传递给函数模板时,编译器会为每一种实际类型实例化出一份独立的代码 。
template<classF,classT>TuseF(F f,T x){staticintcount=0;cout<<"count:"<<++count<<endl;cout<<"count:"<<&count<<endl;returnf(x);}如果在实际调用中,普通函数、仿函数对象和Lambda 表达式,编译器会实例化出三份不同的useF函数 。这种行为会导致代码膨胀,降低编译效率。
std::function本质是一个类模板(包装器/适配器) 。它通过类型擦除技术,能够在统一的接口下存储和调用任何具有相同调用签名的可调用对象。使用std::function接收参数,可以强制所有兼容的可调用对象转换为同一种类型,从而避免模板被多次实例化。
2. std::function 语法与应用解析
std::function的模板参数是函数的签名:std::function<返回值类型(参数类型列表)>。
#include<iostream>#include<functional>usingnamespacestd;// 1. 普通函数intf(inta,intb){returna+b;}// 2. 仿函数structFunctor{intoperator()(inta,intb){returna+b;}};// 3. 类,包含静态与非静态成员函数classPlus{public:staticintplusi(inta,intb){returna+b;}doubleplusd(doublea,doubleb){returna+b;}};intmain(){// 包装普通可调用对象(函数指针、仿函数、Lambda)function<int(int,int)>f1=f;function<int(int,int)>f2=Functor();function<int(int,int)>f3=[](inta,intb){returna+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;// 传入匿名对象return0;}关键点说明:包装类的非静态成员函数时,必须使用&类名::函数名的严格语法取地址。同时,由于非静态成员函数依赖this指针,其实际参数列表比显式参数多一个对象指针/对象参数,这一点在定义function类型时必须体现。
二、 std::bind:参数绑定与调用适配
1. std::bind 的核心机制
std::bind是一个函数模板,它接收一个可调用对象,并生成一个新的可调用对象 。它的核心作用是对现有函数的参数进行重新映射、固定或调整顺序。
从底层来看,std::bind返回的是一个未指定类型的仿函数对象。当我们调用这个生成的仿函数时,它会将我们传入的参数与内部绑定的参数进行整合,最终去调用原始的可调用对象。
2. std::bind 的语法与占位符
std::bind使用std::placeholders命名空间中的占位符(如_1,_2)来表示新生成函数的参数位置 。_1代表新函数的第一个参数,_2代表第二个,以此类推 。
#include<iostream>#include<functional>usingnamespacestd;usingplaceholders::_1;usingplaceholders::_2;usingplaceholders::_3;intSub(inta,intb){return(a-b)*10;}intSubX(inta,intb,intc){return(a-b-c)*10;}classPlus{public:doubleplusd(doublea,doubleb){returna+b;}};intmain(){// 1. 调整参数顺序// _2 代表 sub2 被调用时的第二个参数,_1 代表第一个参数// 当执行 sub2(10, 5) 时,参数映射为 Sub(5, 10)autosub2=bind(Sub,_2,_1);cout<<sub2(10,5)<<endl;// 2. 调整参数个数(固定参数/偏函数应用)// 绑死第一个参数为 100,生成一个只需要一个参数的新函数autosub3=bind(Sub,100,_1);cout<<sub3(5)<<endl;// 映射为 Sub(100, 5)autosub4=bind(Sub,_1,100);cout<<sub4(5)<<endl;// 映射为 Sub(5, 100)// 对于多参函数,可以任意绑死某几个参数autosub5=bind(SubX,100,_1,_2);cout<<sub5(5,1)<<endl;// SubX(100, 5, 1)autosub6=bind(SubX,_1,100,_2);cout<<sub6(5,1)<<endl;// SubX(5, 100, 1)autosub7=bind(SubX,_1,_2,100);cout<<sub7(5,1)<<endl;// SubX(5, 1, 100)// 3. 配合 std::function 绑死类成员函数的 this 指针// 将对象 Plus() 作为隐藏的 this 指针绑死在第一个参数位置// 这样成员函数就可以像普通函数一样,被包装为只需要两个 double 参数的 functionfunction<double(double,double)>f7=bind(&Plus::plusd,Plus(),_1,_2);cout<<f7(1.1,1.1)<<endl;return0;}关键点说明:bind绑定类对象时(如Plus()),通常是值拷贝。如果需要引用绑定,需要使用std::ref或直接传递对象的指针。使用bind将成员函数的this参数固定后,极大地简化了其在回调函数、事件机制中的调用接口。
三、 利用 bind 实现参数柯里化
柯里化:将多参数函数转换为一系列单参数函数的技术,使得函数可以逐步接收参数并支持参数复用和函数组合。
std::bind在工程实践中常用于降低函数的调用维度。例如,将一个通用的计算函数,通过绑定固定参数,特化为特定业务场景的具体函数。
#include<iostream>#include<functional>usingnamespacestd;usingplaceholders::_1;intmain(){// 基础计算模型:计算指定利率、本金、年限下的复利净利润autofunc1=[](doublerate,doublemonty,intyear)->double{doubleret=monty;for(inti=0;i<year;i++){ret+=ret*rate;}returnret-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;return0;}通过这种方式,底层复杂的通用接口被解耦并转化为更高层、更具体的业务接口,这也是函数式编程思维在 C++ 中的具体体现。
四、 核心总结
std::function的核心在于类型统一,解决了模板函数因接收不同可调用类型而导致的实例化膨胀问题 。非静态成员函数在被包装时,由于包含隐藏的this指针,必须显式传递对象或指针参数。std::bind的核心在于接口适配,通过内部生成仿函数并映射参数位置,可以调整函数参数的顺序、固定部分参数,或是抹平类成员函数的对象依赖 。返回的未命名类型通常使用auto接收,或转换并赋值给签名匹配的std::function对象。