译者:yurunsun@gmail.com 新浪微博@孙雨润 新浪博客 优快云博客日期:2012年11月14日
原作:Scott Meyers
这些是Scott Meyers培训教程《新C++标准:C++0x教程》的官方笔记,培训课程的描述请见 http://www.aristeia.com/C++0x.html,版权信息请见 http://aristeia.com/Licensing/licensing.html.
漏洞和建议请发送邮件到 smeyers@aristeia.com. 翻译错误请发送邮件给yurunsun@gmail.com (译者注).
7. lambda
表达式
7.1 lambda
能在需要用的时候快速创建函数对象
std::vector<int> v;
…
auto it = std::find_if(v.cbegin(), v.cend(),
[](int i) { return i > 0 && i < 10; });
相当于c++98中的:
class MagicType1 {
public:
bool operator()(int i) const { return i > 0 && i < 10; }
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType1());
再举一个例子:
typedef std::shared_ptr<Widget> SPWidget;
std::deque<SPWidget> d;
…
std::sort(d.begin(), d.end(), [](const SPWidget& sp1, const SPWidget& sp2) { return *sp1 < *sp2; });
相当于c++98中的:
class MagicType2 {
public:
bool operator()(const SPWidget& p1, const SPWidget& p2) const { return *p1 < *p2; }
};
std::sort(d.begin(), d.end(), MagicType2());
7.2 lambda
函数中的变量
std::function<bool(int)> returnLambda(int a) { // return type to be discussed soon
int b, c;
...
// 编译不通过,报错 not captured
return [](int x) { return a*x*x + b*x + c == 0; };
}
auto f = returnLambda(10); // f is essentially a
// copy of λ’s closure
if (f(22)) ... // “invoke the lambda”
使用静态变量可以解决这个问题:
#include <iostream>
#include <functional>
using namespace std;
int a = 1;
function<bool(int)> returnLambda() {
static int b, c;
return [](int x) {
return a*x*x + b*x + c == 0;
};
}
int main()
{
cout << "Hello World!" << endl;
auto f = returnLambda();
if(f(22)) {
cout << " great! " << endl;
};
return 0;
}
总结一些lambda
表达式中针对变量的规则:
7.2.1 调用上下文中的局部变量必须能被捕捉(captured)
std::function<bool(int)> returnLambda(int a) {
int b, c;
...
return [](int x){ return a*x*x + b*x + c == 0; };
} // 编译器需要capture a, b, c; 因此无法编译通过
7.2.2 没有使用局部变量的lambda
总是合法的:
int a;
std::function<bool(int)> returnLambda() {
static int b, c;
...
return [](int x){ return a*x*x + b*x + c == 0; };
} // 不需要capture a, b, c
7.2.3 使用变量可通过传值或传引用的方式
{
int minVal;
double maxVal;
…
auto it = std::find_if(v.cbegin(), v.cend(),
[minVal, maxVal](int i) { return i > minVal && i < maxVal; });
}
{
int minVal;
double maxVal;
…
auto it = std::find_if( v.cbegin(), v.cend(),
[&minVal, &maxVal](int i){ return i > minVal && i < maxVal; });
}
相当于C++98中的:
class MagicType {
public:
MagicType(int v1, double v2): _minVal(v1), _maxVal(v2) {}
bool operator()(int i) const { return i > _minVal && i < _maxVal; }
private:
int _minVal;
double _maxVal;
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType(minVal, maxVal));
class MagicType {
public:
MagicType(int& v1, double& v2): _minVal(v1), _maxVal(v2) {}
bool operator()(int i) const { return i > _minVal && i < _maxVal; }
private:
int& _minVal;
double& _maxVal;
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType(minVal, maxVal));
7.2.4 默认Capture方式可以指定
auto it = std::find_if(v.cbegin(), v.cend(), {return i > minVal && i < maxVal;}); // 默认传值
auto it = std::find_if(v.cbegin(), v.cend(), [&](int i) {return i > minVal && i < maxVal;}); // 默认传引用
如果提供了上述默认Capture方式,变量(例如minVal maxVal
)可以不必列出。
显示指定某个变量的Capture方式,能够覆盖默认Capture方式:
// 默认传值,对于maxVal变量传引用
auto it = std::find_if(v.cbegin(), v.cend(), [=, &maxVal](int i){ return i > minVal && i < maxVal; });
相当于C++98中的:
class MagicType {
public:
MagicType(int v1, double& v2): _minVal(v1), _maxVal(v2) {}
bool operator()(int i) const { return i > _minVal && i < _maxVal; }
private:
int _minVal;
double& _maxVal;
};
auto it = std::find_if(v.cbegin(), v.cend(), MagicType(minVal, maxVal));
7.2.5 如何去Capture类的成员变量
可以通过capture this的方式达到访问成员变量的目的:
class Widget {
public:
void doSomething();
private:
std::list<int> li;
int minVal;
};
void Widget::doSomething() {
auto it = std::find_if(li.cbegin(), li.cend(), [minVal](int i) { return i > minVal; }); // error!
…
}
void Widget::doSomething() {
auto it = std::find_if(li.cbegin(), li.cend(), [this](int i) { return i > minVal; }); // fine
…
}
上边例子中lambda
被用在成员函数中声明了一个函数对象,范围是类的内部,因此不需要friend
修饰,lambda
能够访问类的所有成员。
对this
的capture方式同样可以指定传值还是传引用:
class Widget {
public:
void doSomething();
private:
std::list<int> li;
int minVal;
};
void Widget::doSomething() {
auto it = std::find_if(li.cbegin(), li.cend(), [=](int i) { return i > minVal; });
…
}
void Widget::doSomething() {
auto it = std::find_if(li.cbegin(), li.cend(), [&](int i) { return i > minVal; });
…
}
对于this
指针,传值要比传引用高效,因为传引用导致二次寻址,虽然这属于编译器优化的能力范围之内。
7.3 lambda
返回值
有以下几个选项:
- 返回
void
lambda
的表达式是return expression
形式,返回类型为expression
的类型- 通过trailing return type语法来指定
最后一点中的trailing return type意思是只有指定实参之后,才能确定它返回什么类型,在一开始的C++0x草案中被称为“late-specified return type”。trailing return type更详细的规则是:
- 必须用在
lambda
表达式中用于指定返回值 - 经常与
decltype
一起使用 -
与
auto
配合使用可以用于任何函数void f(int x); // traditional syntaxauto f(int x)->void; // same declaration with trailing return type class Widget { public: void mf1(int x); // traditional auto mf2() const -> bool; // trailing return type};
7.4 无参数的形式的lambda
会忽略参数列表
void doWork(int x, int y);
void doMoreWork();
std::thread t1([]() { doWork(10, 20); doMoreWork(); }); // w/empty param list
std::thread t2([] { doWork(10, 20); doMoreWork(); }); // w/o empty param list
7.5 复杂的lambda
表达式
除了无法直接使用this
指针外,lambda
表达式中可以和任何函数一样复杂;从可维护的角度考虑,短小清晰、与上下文相关的lambda
比较合适。
7.6 保存lambda
闭包(表达式)
闭包类型的作用域是包含此lambda
的最小{}/class/namespace
。
7.6.1 auto
auto multipleOf5 = [](long x) { return x % 5 == 0; };
std::vector<long> vl;
…
vl.erase(std::remove_if(vl.begin(), vl.end(), multipleOf5), vl.end());
7.6.2 std::function
std::function<bool(long)> multipleOf5 = // see next page for syntax
[](long x) { return x % 5 == 0; };
…
vl.erase(std::remove_if(vl.begin(), vl.end(), multipleOf5), vl.end());
lambda
不能直接递归,但是可以借助std::function
完成递归的效果:
std::function<int(int)> factorial = [&](int x) { return (x==1) ? 1 : (x * factorial(x-1)); };
7.6.3 函数类型
一个函数的类型可以通过声明确定:
void f1(int x); // type is void(int)
double f2(int x, std::string& s); // type is double(int, std::string&)
std::function
的类型表示法:
std::function<bool(long)> multipleOf5 = [](long x) { return x % 5 == 0; }; // from prior slide
或者使用trailing return type
:
std::function<auto (long)->bool> multipleOf5 = [](long x) { return x % 5 == 0; };
7.6.4 auto
与std::function
的比较
auto
更加简洁,但是有些时候auto
无法代替std::function
:
void useIt(auto func); // error!
void useIt(std::function<bool(long)> func); // fine
template<typename Func>
void useIt(Func func); // fine, but generates multiple functions
auto makeFunc(); // error!
std::function<bool(long)> makeFunc(); // fine
template<typename Func>
Func makeFunc(); // fine, but generates multiple functions, and callers must specify Func
auto
不允许作为类的成员变量:
class Widget {
private:
auto func; // error!
...
};
class Widget {
private:
std::function<bool(long)> f; // fine
...
};
至于auto
和std::function
效率比较,Stephan T. Lavavej说“编译器必须表现的足够勇武,才能使std::function
变得和auto
效率相近。
7.6.5 保存的lambda
闭包可能会导致野指针/悬空引用
也就是已经被删除的堆上的对象指针,或者超过作用域的对象的引用。
std::vector<long> vl;
std::function<bool(long)> f;
{
int divisor; some block
…
f = [&](long x) { return x % divisor == 0; }; // closure refers to local var
} // local var’s
// scope ends
vl.erase(std::remove_if(vl.begin(), vl.end(), f), vl.end());// calls to f use dangling ref!
7.6.6 保存的lambda
作为容器比较函数
auto cmpFnc = [](int *pa, int *pb) { return *pa < *pb; }; // compare values, not pointers
std::set<int*, decltype(cmpFnc)> s(cmpFnc); // sort s that way
闭包不会自动构造,因此下边的语法编译报错:
std::set<int*, decltype(cmpFnc)> s; // error! comparison object can't be constructed
7.7 lambda
与闭包的总结
lambda
产生闭包- 调用使用的变量可以以传值或者传引用的方式被capture
- 使用trailing return type的方式指定返回值
- 可以使用
auto
和std::function
来存储闭包,要当心悬空引用和野指针 - 推荐撰写必要的简洁清晰的
lambda
表达式
8. 模板别名
8.1 使用using
可以声明模板别名。
template<typename T>
using MyAllocVec = std::vector<T, MyAllocator>;
MyAllocVec<int> v; // std::vector<int, MyAllocator>
template<std::size_t N>
using StringArray = std::array<std::string, N>;
StringArray<15> sa; // std::array<std::string, 15>
template<typename K, typename V>
using MapGT = std::map<K, V, std::greater<K>>;
// std::map<long long, std::shared_ptr<std::string>, std::greater<long long>>
MapGT<long long, std::shared_ptr<std::string>> myMap;
8.2 模板别名不能被特化:
template<typename T>
using MyAllocVec = std::vector<T, MyAllocator>; // fine
template<typename T>
using MyAllocVec = std::vector<T*, MyPtrAllocator>; // error!
可以使用trait class
达到特化的目的:
template<typename T> // primary template
struct VecAllocator {
typedef MyAllocator type;
};
template<typename T> // specialized template
struct VecAllocator<T*> {
typedef MyPtrAllocator type;
};
template<typename T>
using MyAllocVec = std::vector<T, typename VecAllocator<T>::type>;
8.3 using
能做typedef
能做的事情,而且可读性更好:
typedef std::unordered_set<int> IntHash; // these 2 lines do the same thing
using IntHash = std::unordered_set<int>;
typedef void (*CallBackPtr)(int); // func. ptr. typedef equivalent using decl.
using CallBackPtr = void (*)(int);