1. Lambda函数的用处
lambda 表达式可以方便地构造匿名函数,如果你的代码里面存在大量的小函数,而这些函数一般只被调用一次,那么不妨将他们重构成 lambda 表达式。
C++11 的 lambda 表达式规范如下:
[ capture ] ( params ) mutable exception attribute -> ret { body } | (1) |
[ capture ] ( params ) -> ret { body } | (2) |
[ capture ] ( params ) { body } | (3) |
[ capture ] { body } | (4) |
其中
-
(1) 是完整的 lambda 表达式形式,
-
(2) const 类型的 lambda 表达式,该类型的表达式不能修改捕获("capture")列表中的值。
-
(3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来:
-
如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。
-
如果没有 return 语句,则类似 void f(...) 函数。
-
-
省略了参数列表,类似于无参函数 f()。
mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。
exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f() throw(X, Y)。
attribute 用来声明属性。
另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表(详见第三节),具体解释如下:
-
[a,&b] a变量以值的方式被捕获,b以引用的方式被捕获。
-
[this] 以值的方式捕获 this 指针。
-
[&] 以引用的方式捕获所有的外部自动变量。
-
[=] 以值的方式捕获所有的外部自动变量。
-
[] 不捕获外部的任何变量。
此外,params 指定 lambda 表达式的参数。
2. Lambda函数中的变量截取
捕获:
(1)值捕获
与参数传值异同,
相同点:值捕获的前提是变量可以拷贝
不同点:被捕获的变量在lambda被创建时拷贝,而非调用时才拷贝
void f(const int*);
void g()
{
const int N = 10;
[=]{
int arr[N]; // not an odr-use: refers to g's const int N
f(&N); // odr-use: causes N to be captured (by copy)
// &N is the address of the closure object's member N, not g's N
}();
}
(2)引用捕获
与引用传参类似,引用捕获保存的是引用,值会发生变化。
#include <iostream>
auto make_function(int& x) { return [&]{ std::cout << x << '\n'; }; //return [=]{ std::cout << x << '\n'; }; //值捕获打印3,创建时传入的值是多少就是多少 }
int main() {
int i = 3;
auto f = make_function(i); // the use of x in f binds directly to i
i = 5;
f(); // OK; prints 5
}
(3)隐式捕获
[] 空捕获列表
[name1, name2, ...] 捕获一系列变量
[&] 引用捕获, 让编译器自行推导捕获列表
[=] 值捕获, 让编译器执行推导应用列表
void f3() {
float x, &r = x;
[=]
{ // x and r are not captured (appearance in a decltype operand is not an odr-use)
decltype(x) y1; // y1 has type float
decltype((x)) y2 = y1; // y2 has type float const& because this lambda
// is not mutable and x is an lvalue
decltype(r) r1 = y1; // r1 has type float& (transformation not considered)
decltype((r)) r2 = y2; // r2 has type float const&
};
}
当然C++14,C++17,C++20对lambda做了更多的更新,这里就不多讲了。
4. Lambda函数和STL
lambda函数的引入为STL的使用提供了极大的方便。比如下面这个例子,当你想便利一个vector的时候,原来你得这么写:
vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )
{
cout << *itr;
}
现在有了lambda函数你就可以这么写:
vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for_each( v.begin(), v.end(), [] (int val)
{
cout << val;
} );
而且这么写了之后执行效率反而提高了。因为编译器有可能使用”循环展开“来加速执行过程。
仿函数(functor)
什么是仿函数(functor)
functor的英文解释为something that performs a function,即其行为类似函数的东西。C++中的仿函数是通过在类中重载()运算符实现,使你可以像使用函数一样来创建类的对象。
仿函数(functor)的实现及使用
// this is a functor
struct add_x {
add_x(int x) : x(x) {}
int operator()(int y) { return x + y; }
private:
int x;
};
// usage:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument
std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out;
// Pass a functor to std::transform, which calls the functor on every element
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
assert(out[i] == in[i] + 1); // for all i
为什么使用仿函数(functor)
-
迭代和计算逻辑分离
使用仿函数可以使迭代和计算分离开来。因而你的functor可以应用于不同场合,在STL的算法中就大量使用了functor,下面是STL中for_each中使用functor的示例:
struct sum
{
sum(int * t):total(t){};
int * total;
void operator()(int element)
{
*total+=element;
}
};
int main()
{
int total = 0;
sum s(&total);
int arr[] = {0, 1, 2, 3, 4, 5};
std::for_each(arr, arr+6, s);
cout << total << endl; // prints total = 15;
}
-
参数可设置
可以很容易通过给仿函数(functor)设置参数,来实现原本函数指针才能实现的功能,看下面代码:
class CalculateAverageOfPowers
{
public:
CalculateAverageOfPowers(float p) : acc(0), n(0), p(p) {}
void operator() (float x) { acc += pow(x, p); n++; }
float getAverage() const { return acc / n; }
private:
float acc;
int n;
float p;
};
这个仿函数的功能是求给定值平方或立方运算的平均值。只需要这样来声明一个对象即可:
CalculateAverageOfPowers my_cal(2);
-
有状态
与普通函数另一个区别是仿函数(functor)是有状态的,所以可以进行诸如下面这种操作:
CalculateAverage avg;
avg = std::for_each(dataA.begin(), dataA.end(), avg);
avg = std::for_each(dataB.begin(), dataB.end(), avg);
avg = std::for_each(dataC.begin(), dataC.end(), avg);
对多个不同的数据集进行取平均。
-
性能
我们看一下2中写的代码:
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
编译器可以准确知道std::transform需要调用哪个函数(add_x::operator)。这意味着它可以内联这个函数调用。而如果使用函数指针,编译器不能直接确定指针指向的函数,而这必须在程序运行时才能得到并调用。
一个例子就是比较std::sort 和qsort ,STL的版本一般要快5-10倍。
-
总结
当然,前3点都可以使用传统的函数和指针实现,但是用仿函数(functor)可以让这种实现变的更加简单。
函数对象包装器之std::function
std::function 是C++11中又一个新的概念。想想在之前的C++中,function一直是一个尴尬的存在,它作为程序的一部分,被“钉死”在程序的代码段,而且可调用的对象五花八门。
C++11打破了这个尴尬,统一可调用对象的概念,可调用对象有以下几种定义:
-
是一个函数指针,参考 C++ 函数指针和函数类型;
-
是一个具有operator()成员函数的类的对象;
-
可被转换成函数指针的类对象;
-
一个类成员函数指针;
C++中可调用对象的虽然都有一个比较统一的操作形式,但是定义方法五花八门,这样就导致使用统一的方式保存可调用对象或者传递可调用对象时,会十分繁琐。C++11中提供了std::function和std::bind统一了可调用对象的各种操作。
举个例子:
// 普通函数
int add(int a, int b){return a+b;}
// lambda表达式
auto mod = [](int a, int b){ return a % b;}
// 函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};
上述三种可调用对象虽然类型不同,但是共享了一种调用形式:
int(int ,int)
std::function就可以将上述类型保存起来,如下:
std::function<int(int ,int)> a = add;
std::function<int(int ,int)> b = mod ;
std::function<int(int ,int)> c = divide();
总结一下:
std::function是一个类模版,是一种通用、多态的函数封装。
std::function对象可以对普通函数、Lambda表达式、函数指针、以及其它函数对象等 进行存储、复制、和调用操作。
std::function对象是对可调用实体类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。
std::function通常是一个函数对象类,它包装其它任意的函数对象,被包装的函数对象具有类型为T1, …,TN的N个参数,并且返回一个可转换到R类型的值。
std::function使用 模板转换构造函数接收被包装的函数对象;特别是,闭包类型可以隐式地转换为std::function。
最简单的理解就是:
通过std::function对可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象;让我们不再纠结那么多的可调用实体
下边贴段代码加深一下理解:
#include <functional>
#include <iostream>
using namespace std;
std::function< int(int) > Functional;
// 普通函数
int TestFunc(int a)
{
return a;
}
// Lambda表达式
auto lambda = [](int a)->int{ return a; };
// 仿函数(functor)
class Functor
{
public:
int operator()(int a)
{
return a;
}
};
// 1.类成员函数
// 2.类静态函数
class TestClass
{
public:
int ClassMember(int a) { return a; }
static int StaticMember(int a) { return a; }
};
int main()
{
// 普通函数
Functional = TestFunc;
int result = Functional(10);
cout << "普通函数:"<< result << endl;
// Lambda表达式
Functional = lambda;
result = Functional(20);
cout << "Lambda表达式:"<< result << endl;
// 仿函数
Functor testFunctor;
Functional = testFunctor;
result = Functional(30);
cout << "仿函数:"<< result << endl;
// 类成员函数
TestClass testObj;
Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
result = Functional(40);
cout << "类成员函数:"<< result << endl;
// 类静态函数
Functional = TestClass::StaticMember;
result = Functional(50);
cout << "类静态函数:"<< result << endl;
return 0;
}