1、std::function介绍
类模板std::function是一种通用的、多态的函数封装。
std::function的实例可以对任何可以调用的目标实体进行存储、复制、调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其他函数对象等。std::function对象是对c++中现有的可调用实体的一种类型安全的包裹(函数指针这类可调用实体,是类型不安全的)。
通常std::function是一个函数对象类,它包装其他任意的函数对象,被包装的函数对象具有类型为T1,...,Tn的n个参数,并且返回一个可转换到R类型的值。std::function使用模板转换构造函数接收被包装的函数对象;
特别是,闭包类型可以隐式地转换为std::function。
std::function统一和简化了相同类型可调用实体的使用方式,使编码变得更简单。
简单的理解就是:
通过std::function对c++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象;让我们不再纠结那么多的可调用实体
2、std::function的原型
template<class R, class ... Args> class function<R(Args...)>
R是返回值类型,Args是函数的参数类型,实例一个std::function对象很简单,就是将可调用对象的返回值类型和参数类型作为模板参数传递给std::function模板类。例如
std::function<void()> f1;---->void f1(); std::function<int (int , int)> f2;---->int f2(int,int)
3、std::function的用法
包含于头文件#include<functional>中,可将各种可调用实体进行统一封装,包括
普通函数、lambda表达式、函数指针、仿函数(functor重载括号运算符实现)、类成员函数、静态成员函数
代码:
#include <iostream> #include <functional> using namespace std; std::function<bool(int, int)> fun; //普通函数 bool compare_com(int a, int b) { return a > b; } //lambda表达式 auto compare_lambda = [](int a, int b){ return a > b;}; //仿函数 class compare_class { public: bool operator()(int a, int b) { return a > b; } }; //类成员函数 class compare { public: bool compare_member(int a, int b) { return a > b; } static bool compare_static_member(int a, int b) { return a > b; } }; int main() { bool result; fun = compare_com; result = fun(10, 1); cout << "普通函数输出, result is " << result << endl; fun = compare_lambda; result = fun(10, 1); cout << "lambda表达式输出, result is " << result << endl; fun = compare_class(); result = fun(10, 1); cout << "仿函数输出, result is " << result << endl; fun = compare::compare_static_member; result = fun(10, 1); cout << "类静态成员函数输出, result is " << result << endl; ////类普通成员函数比较特殊,需要使用bind函数,并且需要实例化对象,成员函数要加取地址符 compare temp; fun = std::bind(&compare::compare_member, temp, std::placeholders::_1, std::placeholders::_2); result = fun(10, 1); cout << "类普通成员函数输出, result is " << result << endl; }
4、std::function使用注意事项
可见std::function的使用其实是很简单的,只要创建一个模板类对象,并传入相应的模板参数就可以存储任何具有相同返回值和参数的可调用对象,在调用的时候直接将std::fuction对象加上()或加入参数就可以调用存储在其中的可调用实体。
1)关于可调用实体转换为std::function对象需要遵守以下两条原则:
a、转换后的std::function
对象的参数能转换为可调用实体的参数
b、可调用实体的返回值能转换为std::function
对象的返回值。
2)std::function对象最大的用处就是在实现函数回调,使用者需要注意,它不能被用来检查相等或者不相等,但是可以与NULL或nullptr进行比较
3)需要注意的是创建的std::function对象中存储的可调用实体不能为空,若对空的std::function进行调用将抛出std::bad_function_异常。
5、std::bind函数将可调用对象和可调用对象的参数进行绑定,返回新的可调用对象(std::function类型,参数列表可能改变),返回的新的std::function可调用对象的参数列表根据bind函数实参中std::palceholders::_x从小到大对应的参数确定。
#include <iostream> using namespace std; class A { public: void fun_3(int k,int m) { cout<<k<<" "<<m<<endl; } }; void fun(int x,int y,int z) { cout<<x<<" "<<y<<" "<<z<<endl; } void fun_2(int &a,int &b) { a++; b++; cout<<a<<" "<<b<<endl; } int main(int argc, const char * argv[]) { auto f1 = std::bind(fun,1,2,3); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3 f1(); //print:1 2 3 auto f2 = std::bind(fun, placeholders::_1,placeholders::_2,3); //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f2 的第一,二个参数指定 f2(1,2);//print:1 2 3 auto f3 = std::bind(fun,placeholders::_2,placeholders::_1,3); //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f3 的第二,一个参数指定 //注意: f2 和 f3 的区别。 f3(1,2);//print:2 1 3 int n = 2; int m = 3; auto f4 = std::bind(fun_2, n,placeholders::_1); f4(m); //print:3 4 cout<<m<<endl;//print:4 说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的 cout<<n<<endl;//print:2 说明:bind对于预先绑定的函数参数是通过值传递的 A a; auto f5 = std::bind(&A::fun_3, a,placeholders::_1,placeholders::_2); f5(10,20);//print:10 20 std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2); fc(10,20);//print:10 20 return 0; }
6、lambda表达式的捕获方式由哪几种
1)简述
c++11的一大优点就是引入了lambda表达式;
利用lambda表达式,可以方便的定义和创建匿名函数。
2)声明lambda表达式
[capture list] (params list) mutable exception-> return type {function body}
含义:
1、capture list:捕获外部变量列表
2、params list:形参列表
3、mutable指示符:用来说明是否可以修改捕获的变量
4、exception:异常设定
5、return type:返回类型
6、function body:函数体
此外,我们还可以省略其中的某些成分来声明"不完整"的lambda表达式,常见的有以下几种:
1、[capture list] (params list) -> return type{function body}
2、[capture list](params list){function body}
3、[capture list] {function body}
其中:
格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值。
格式2省略了返回值类型,但编译器可以根据以下规则推断出lambda表达式的返回类型:
1)如果function body中存在return语句,则该lambda表达式的返回类型由return语句的返回类型确定;
2)如果function body中没有return语句,则返回值为void类型;
格式3中省略了参数列表,类似普通函数总无参数函数。
代码:
#include <iostream> #include <vector> #include <algorithm> using namespace std; bool cmp(int a, int b) { return a < b; } int main() { vector<int> myvec{ 3, 2, 5, 7, 3, 2 }; vector<int> lbvec(myvec); sort(myvec.begin(), myvec.end(), cmp); // 旧式做法 cout << "predicate function:" << endl; for (int it : myvec) cout << it << ' '; cout << endl; sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; }); // Lambda表达式 cout << "lambda expression:" << endl; for (int it : lbvec) cout << it << ' '; }
在c++11之前,我们使用STL的sort函数,需要提供一个谓词函数。如果使用c++11的Lambda表达式,只需要传入一个匿名函数即可,方便简洁,且代码的可读性也比旧式的做法好。
Lambda表达式各项的具体用法
1)捕获外部变量
Lambda表达式可以使用其可见范围内的外部变量,但必须明确声明(明确声明哪些外部变量可以被该Lambda表达式使用)。那么,在哪里指定这些外部变量呢?Lambda表达式通过在最前面的方括号[]来明确指明其内部可以访问的外部变量,这一过程也称为lambda表达式捕获了外部变量。
#include <iostream> using namespace std; int main() { int a = 123; auto f = [a] { cout << a << endl; }; //此处并不会输出,此处相当于声明lambda表达式 f(); // 输出:123 //或通过“函数体”后面的‘()’传入参数 auto x = [](int a)-> int {cout << a << endl;}(123); }
上面这个例子先声明了一个整型变量a,然后再创建lambda表达式,该表达式“捕获”了a变量,这样在lambda表达式函数体中就可以获得该变量的值。
类似参数传递方式(值传递、引入传递、指针传递),在lambda表达式中,外部变量的捕获方式也有值捕获、引用捕获、隐式捕获。
值捕获
值捕获和参数传递中的值传递类似,被捕获的变量的值在lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响lambda表达式中的值。
代码
int main() { int a = 123; auto f = [a] { cout << a << endl; }; //此处并不会输出 a = 321; f(); // 输出:123 }
需要注意的是,如果以值传递方式捕获外部变量,则在lambda表达式函数体中不能修改该外部变量的值;
引用捕获
使用引用捕获一个外部变量,只需要在捕获列表变量前面加上一个引用说明符&,如下
#include <iostream> using namespace std; int main() { int a = 123; auto f = [&a](){cout << a << endl;a = 59; cout << a << endl;}; cout << a << endl; a = 321; f(); return 0; } 输出 123 321 59
隐式捕获
上面的值捕获和引用捕获都需要我们在捕获列表中显示列出lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。
隐式值捕获实例
int main() { int a = 123; auto f = [=] { cout << a << endl; }; // 值捕获 f(); // 输出:123 }
隐式引用捕获实例
int main() { int a = 123; auto f = [&]{cout <<a<<endl;};//引用捕获 a = 321; f();//输出321 }
混合方式
上面的例子,要么值捕获,要么引用捕获,Lambda表达式还支持混合的方式捕获外部变量,这种方式主要是以上几种捕获方式的组合使用。
c++11捕获外部变量总结
[] :不捕获任何外部变量
[变量名,...]:默认以值的形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符)
[this]:以值的形式捕获this指针
[=]:以值的形式捕获所有外部变量
[&]:以引用的形式捕获所有外部变量
[=,&x]:变量x以引用形式捕获,其余变量以传值形式捕获
[&,x]:变量x以值的形式捕获,其余变量以引用形式捕获
修改捕获变量
前面提到过,在lambda表达式中,如果以传值方式捕获外部变量,则函数体中不能修改该外部变量,否则会引发编译错误。那么有没有办法可以修改值捕获的外部变量呢,这就需要使用mutable关键字,该关键字用以说明表达式体内的代码可以修改值捕获的变量,例如
int main() { int a = 123; auto f = [a]()mutable{cout <<++a;};//不会报错 cout <<a << endl;//输出123 f();//输出124 }
lambda表达式的参数
Lambda表达式的参数和普通函数的参数类似,那么这里为什么还要拿出来说一下呢?原因是在Lambda表达式中传递参数还有一些限制,主要有以下几点:
- 参数列表中不能有默认参数
- 不支持可变参数
- 所有参数必须有参数名
{ int m = [](int x) { return [](int y) { return y * 2; }(x)+6; }(5); std::cout << "m:" << m << std::endl; //输出m:16 std::cout << "n:" << [](int x, int y) { return x + y; }(5, 4) << std::endl; //输出n:9 auto gFunc = [](int x) -> function<int(int)> { return [=](int y) { return x + y; }; }; auto lFunc = gFunc(4); std::cout << lFunc(5) << std::endl; auto hFunc = [](const function<int(int)>& f, int z) { return f(z) + 1; }; auto a = hFunc(gFunc(7), 8); int a = 111, b = 222; auto func = [=, &b]()mutable { a = 22; b = 333; std::cout << "a:" << a << " b:" << b << std::endl; }; func(); std::cout << "a:" << a << " b:" << b << std::endl; a = 333; auto func2 = [=, &a] { a = 444; std::cout << "a:" << a << " b:" << b << std::endl; }; func2(); auto func3 = [](int x) ->function<int(int)> { return [=](int y) { return x + y; }; }; std::function<void(int x)> f_display_42 = [](int x) { print_num(x); }; f_display_42(44); }