C++11之lambda及包装器
C++11
- 一.Lambda
- 1.1Lambda 表达式
- 1.2捕捉列表
- 捕获列表的基本概念
- 捕获方式
- (一)值捕获
- (二)引用捕获
- (三)默认捕获
- (四)捕获其他特性
- (五)在类中捕获私有
- 1.3lambda的应⽤
- 1.4lambda的原理
- 二.包装器
- 2.1function
- `std::function`的声明与定义
- 2.2bind
- bind的声明与定义
- std::bind 的原理
- std::bind的使用

一.Lambda
lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。
1.1Lambda 表达式
Lambda 表达式是一种匿名函数对象,它可以在代码中直接定义并使用。其基本语法结构如下:
[capture](parameters)->return-type { body }
- capture:捕获列表,用于捕获 Lambda 表达式外部的变量。捕获方式有值捕获(
[x]
,将变量 x 的值复制到 Lambda 中)、引用捕获([&x]
,通过引用捕获变量 x)等多种方式。 - parameters:参数列表,与普通函数的参数列表类似,用于接收调用时传入的参数。
- return-type:返回类型,可以省略,如果 Lambda 表达式只有一条语句且该语句有返回值,则返回类型由该语句的返回类型推导得出。
- body:函数体,是 Lambda 表达式的核心部分,用于实现具体的功能逻辑。
1.2捕捉列表
捕获列表的基本概念
捕获列表位于 Lambda 表达式的开头,用方括号[]表示。它允许 Lambda 表达式捕获外部作用域中的变量,从而在 Lambda 表达式的函数体内使用这些变量。捕获列表的语法如下:
[capture1, capture2, …]
捕获列表中的每个捕获项可以是一个变量名、一个变量名前加&符号(表示通过引用捕获),或者是一个变量名前加*符号(表示捕获变量的地址)。捕获项之间用逗号分隔。
捕获方式
(一)值捕获
值捕获是将外部变量的值复制到 Lambda 表达式中。这种方式下,Lambda 表达式内部的变量是外部变量的一个副本,对内部变量的修改不会影响外部变量。例如:
int x = 10;
auto lambda = [x]() {x = 20; // 修改的是 Lambda 内部的 x 副本
};
lambda();
std::cout << x << std::endl; // 输出 10,外部 x 的值未改变
值捕获适用于那些不需要修改外部变量,或者希望在 Lambda 表达式中独立操作变量的情况。
(二)引用捕获
引用捕获是通过引用的方式捕获外部变量。这种方式下,Lambda 表达式内部的变量是外部变量的引用,对内部变量的修改会直接影响外部变量。例如:
int x = 10;
auto lambda = [&x]() {x = 20; // 直接修改外部变量 x
};
lambda();
std::cout << x << std::endl; // 输出 20,外部 x 的值被修改
引用捕获适用于那些需要在 Lambda 表达式中修改外部变量的情况,但需要注意的是,如果外部变量的生命周期短于 Lambda 表达式的生命周期,可能会导致悬空引用,引发运行时错误。
(三)默认捕获
在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=,&x]表⽰其他变量隐式值捕捉,
x引⽤捕捉;[&,x,y]表⽰其他变量引⽤捕捉,x和y值捕捉。
当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时
后⾯的捕捉变量必须是值捕捉,
同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。
除了显式指定捕获的变量外,Lambda 表达式还支持默认捕获。默认捕获有两种方式:
[=]:表示默认按值捕获所有外部变量。
[&]:表示默认按引用捕获所有外部变量。
默认捕获可以简化捕获列表的书写,但需要谨慎使用,因为过度捕获可能会导致不必要的性能开销或潜在的错误。例如:
int x = 10;
int y = 20;
auto lambda = [=]() {x = 30; // 修改的是 Lambda 内部的 x 副本y = 40; // 修改的是 Lambda 内部的 y 副本
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 20,外部 x 和 y 的值未改变
在默认捕获的情况下,如果需要对某个变量采用不同的捕获方式,可以通过显式捕获来覆盖默认捕获。例如:
int x = 10;
int y = 20;
auto lambda = [=, &y]() {x = 30; // 修改的是 Lambda 内部的 x 副本y = 40; // 直接修改外部变量 y
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 40,外部 x 的值未改变,y 的值被修改
在这个例子中,x 按值捕获,y 按引用捕获。
(四)捕获其他特性
- lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
// 局部的静态和全局变量不能捕捉,也不需要捕捉 static int m = 0;auto func6 = []{int ret = x + m;return ret;};
- 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。
i
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了 // mutable相当于去掉const属性,可以修改了 // 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉ auto func7 = [=]()mutable{a++;b++;c++;d++;return a + b + c + d;};cout << func7() << endl;
(五)在类中捕获私有
在类中,若想要使用私有成员变量时,直接捕获私有成员是报错的;这时需要通过捕获this指针来使用私有成员变量。
1.3lambda的应⽤
• 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
• lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等
struct Goods{string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 // ...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};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()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3
}, { "菠萝", 1.5, 4 } };// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中 // 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了 sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());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;});return 0;
}
1.4lambda的原理
• lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
• 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。
二.包装器
2.1function
std::function
是C++标准模板库(STL)中定义在<functional>
头文件中的一个类模板。它能够存储、复制,并调用任何可调用对象,如普通函数、Lambda表达式、函数对象、以及其他函数封装器(如std::bind
)的结果。这使得std::function
成为一种通用的函数封装方式,极大地增强了函数的通用性和可扩展性。
std::function 是⼀个类模板,也是⼀个包装器。std::function 的实例对象可以包装存储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对象被称为std::function 的⽬标。若std::function 不含⽬标,则称它为空。调⽤空std::function 的⽬标导致抛出std::bad_function_call异常。
std::function
的声明与定义
std::function
的声明形式如下:
template <class> class function; // 未定义
template <class Ret, class... Args>
class function<Ret(Args...)>; // 定义
这里Ret
表示函数返回值的类型,Args...
表示函数参数的类型列表。例如,std::function<void()>
表示一个没有参数且没有返回值的函数;std::function<int(int, int)>
表示一个接受两个int
参数并返回一个int
值的函数。
函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型,下⾯的第⼆个代码样例展⽰了 std::function 作为map的参数,实现字符串和可调⽤对象的映射表功能。特别要注意的是在调用普通成员函数
#include<functional>int f(int a, int b)
{return a + b;
}struct Functor{public:int operator() (int a, int b){return a + b;}
};class Plus{public:Plus(int n = 10):_n(n){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return (a + b) * _n;}private:int _n;
};int main()
{// 包装各种可调⽤对象 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<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;return 0;
}
2.2bind
bind的声明与定义
bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在这个头⽂件中。
- 调⽤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的⼀个命名空间中。
using placeholders::_1;using placeholders::_2;using placeholders::_3;
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);
std::bind 的原理
std::bind 的原理基于模板和函数对象。它通过模板参数推导,将函数和参数封装成一个函数对象。这个函数对象重载了 operator(),当调用这个函数对象时,它会按照绑定时的参数和函数进行调用。
在实现上,std::bind 使用了完美转发和模板元编程技术。它能够处理各种类型的参数,包括左值、右值、引用等,并且能够正确地将参数转发到目标函数中。
std::bind的使用
#include<functional>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:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;// bind 本质返回的⼀个仿函数对象 // 调整参数顺序(不常⽤) // _1代表第⼀个实参 // _2代表第⼆个实参 // ...auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;// 调整参数个数 (常⽤) auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数 auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;// 成员函数对象进⾏绑死,就不需要每次都传递了 function<double(Plus&&, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(move(pd), 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;// bind⼀般⽤于,绑死⼀些固定参数 function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;}