现在的位置: 首页 > 综合 > 正文

VC10和C++ 0x (1) – lambda表达式

2013年09月04日 ⁄ 综合 ⁄ 共 5673字 ⁄ 字号 评论关闭


来源: <http://www.cnblogs.com/brucejia/archive/2009/09/05/1560675.html>


【本文大部分内容译自Visual C++ Team Blog】http://blogs.msdn.com/vcblog/archive/2008/10/28/lambdas-auto-and-static-assert-c-0x-features-in-vc10-part-1.aspx

 

尽管C++社区对C++ 0x很是追捧,但是各厂商对于新标准的支持并不热乎。盼星星盼月亮,微软作为Windows平台上最强势的C++编译器厂商也终于在Visual Studio 2010中开始支持C++ 0x的特性。

 

Lambda表达式,auto 和静态断言(static_assert)

Visual Studio 2010中的Visual C++编译器,即VC10, 包含了4个C++ 0x的语言特性 - lambda表达式,auto,static_assert 和 rvalue reference (右值引用).


 

相关链接:

 

lambdas

使用过函数式编程语言(如lisp, F#)或一些动态语言(如Python,Javascript)的大侠对于lambda表达式一定不会陌生。

在C++ 0x中,引入了lambda表达式来定义无名仿函数。下面是一个lambda表达式的简单例子:

// File: meow.cpp

#include
<algorithm>
#include
<iostream>
#include
<ostream>
#include
<vector>

using namespace
std;

int
main() {

    vector<int> v;
for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }


    for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "
; });

    cout << endl;   
return
0;

}

 

C:\Temp>cl /EHsc /nologo /W4 meow.cpp > NUL && meow

0 1 2 3 4 5 6 7 8 9

 

for_each一行中,中括号[]称为lambda introducer, 它告诉编译器接下来的是一个lambda表达式;接下来(int n)是lambda表达式的参数声明;最后大括号里边就是“函数体”了。

注意这里因为lambda表达式生成的是functor,所以“函数体”实际上是指这个functor的operator ()的调用部分。你也许会问:那么返回值呢?缺省情况下lambda表达式生成的functor调用

返回类型为void.

 

所以,可以理解为上边的代码会被编译器翻译成如下:

#include
<algorithm>
#include
<iostream>
#include
<ostream>
#include
<vector>

using namespace
std;

struct
LambdaFunctor {
void operator()(int
n)
const
{

        cout << n <<
" "
;

    }

};

int
main() {

    vector<int> v;

for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }


    for_each(v.begin(), v.end(), LambdaFunctor());

    cout << endl;
return
0;

}
为了方便,以下会用"lambda返回void"的简短表述来代替冗长啰嗦的表述:lambda表达式生成一个functor类型,这个functor类型的函数调用操作符(operator())返回的类型是void.

请大家一定记住:lambda表达式生成了类型,并构造该类型的实例。

 

下面的例子中lambda表达式的“函数体”包含多条语句:

#include
<algorithm>
#include
<iostream>
#include
<ostream>
#include
<vector>

using namespace
std;

int
main() {

    vector<int> v;
for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }


    for_each(v.begin(), v.end(), [](int
n) {

        cout << n;

if
(n % 2 == 0) {

            cout <<
" even "
;

        }
else
{

            cout <<
" odd "
;

        }

    });


    cout << endl;
return
0;

}
上文提到了lambda表达式缺省情况下返回void. 那么如果需要返回其他类型呢?

答案是:lambda表达式的“函数体”中如果有一个return的表达式,例如{ return expression; },那么编译器将自动推演expression的类型作为返回类型。

#include <algorithm> 
#include <deque> 
#include <iostream> 
#include <iterator> 
#include <ostream> 
#include <vector> 

using namespace std; 

int main() { 

    vector<int> v; 


    for (int i
= 0; i < 10; ++i) { 

        v.push_back(i); 

    } 


    deque<int> d; 


    transform(v.begin(), v.end(), front_inserter(d), [](int n) { return n
* n * n; }); 


    for_each(d.begin(), d.end(), [](int n) { cout << n << "
"
; }); 


    cout << endl; 


上例中返回值n * n * n很简单,类型推演是显而易见的。但是如果lambda表达式中有非常复杂的表达式时,编译器可以无法推演出其类型,或者是推演出现二义性,这时候你可以

显式地指明返回值类型。如下所示:

 

transform(v.begin(), v.end(), front_inserter(d),
[](int
n) ->
double
{
if
(n % 2 == 0) {
return
n * n * n;

    }
else
{
return
n / 2.0;

    }

}
);

 

黑体部分中有的“-> double”显式地指明了lambda表达式的返回类型是double.

 

以上例子中的lambda都是无状态的(stateless),不包含任何数据成员。很多时候我们需要lambda包含数据成员以保存状态,这一点可以通过“捕获”(capturing)局部变量来实现。

lambda表达式的导入符(lambda-introducer)是空的,也就是“[]”,表明该lambda是一个无状态的。但是在lambda导入符中可以指定一个“捕获列表”(capture-list)。

 

 

int
main() {

    vector<int> v;
for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }

int
x = 0;
int
y = 0;


    cout <<
"Input: "
;

    cin >> x >> y;

    v.erase(remove_if(v.begin(), v.end(), [x, y](int
n) {
return
x < n && n < y; }), v.end());

    for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "
; });

    cout << endl;

}

 

上边的代码中的lambda使用了局部变量x和y,将值介于x和y之间的元素从集合中删除。

程序运行示例如下 -

Input: 4 7

0 1 2 3 4 7 8 9

 

上边的代码可以理解为:

class
LambdaFunctor {

public:

    LambdaFunctor(int
a,
int
b) : m_a(a), m_b(b) { }
bool operator()(int
n)
const
{
return
m_a < n && n < m_b; }

private:
int
m_a;
int
m_b;

};

int
main() {


    vector<int> v;
for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }

int
x = 0;
int
y = 0;


    cout <<
"Input: "
;

    cin >> x >> y;


    v.erase(remove_if(v.begin(), v.end(), LambdaFunctor(x, y)), v.end());

    copy(v.begin(), v.end(), ostream_iterator<int>(cout,

" "
));

    cout << endl;

}

 

上面代码中很重要的一点信息是:lambda中捕获的局部变量是以“传值”的方式传给匿名函数对象的。在匿名函数对象中,保存有“捕获列表”中局部变量的拷贝。

这一点使得匿名函数对象的生命周期能够长于main中的x,y局部变量。然而这样的传值方式带来几个限制:

  1. lambda中的这两个拷贝并不能被改变,因为缺省情况下函数对象的operator()是const;
  2. 有的对象的拷贝操作开销很大或者不可能(例如如果上面代码中的x, y是数据库链接或者某个singleton)
  3. 即使在lambda内部修改了m_a, m_b也不能够影响外边main函数中的x和y

 

既然有了“传值”,你一定猜到了还会有“传引用”。bingo! 你是对的。

在讨论“传引用”之前,我们先来看看另一个比较有用的东西。假设你有一大堆的局部变量需要被lambda使用,那么你的“捕获列表”将会写的很长,这肯定不是件愉快的事情。

好在C++委员会的老头们也想到了,C++ 0x中提供了一个省心的东西:如果捕获列表写成 [=],表示lambda将捕获所有的局部变量,当然也是传值方式。这种方式姑且被称为“缺省捕获”(capture-default)。

 

int
main() {


    vector<int> v;
for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }

int
x = 0;
int
y = 0;


    cout <<
"Input: "
;

    cin >> x >> y;
// EVIL!
v.erase(remove_if(v.begin(), v.end(), [=](int
n) {
return
x < n && n < y; }), v.end());

    for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "
; });

    cout << endl;

}

 

当编译器在lambda的作用范围内看到局部变量x, y时,它会以传值的方式从main函数中将他们捕获。

下面我们来看如何突破前面提到的3点限制。

 

第一点,修改lambda表达式中的局部变量拷贝(e.g. m_a, m_b)

缺省情况下,lambda的operator ()是const 修饰的,但是你可以使用mutable关键字改变这一点。

 

int
main() {


    vector<int> v;
for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }

int
x = 1;
int
y = 1;


    for_each(v.begin(), v.end(), [=](int& r)

mutable
{
const int
old = r;

        r *= x * y;

        x = y;

        y = old;

    });


    for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "
; });

    cout << endl;

    cout << x <<
", "
<< y << endl;

}

 

代码运行结果如下

0 0 0 6 24 60 120 210 336 504

1, 1

 

这里我们解决了第一个限制,但是却产生了一个新的限制

4.  lambda中对捕获变量的修改并不会影响到main函数中的局部变量,因为lambda捕获局部变量使用的是传值方式

 

下面该“传引用”的方式登场了,它能够有效地解决2,3,4三个限制。

 

传引用的语法为: lambda-introducer [&x, &y]

这里的捕获列表应该理解为:X& x, Y& y ; 因为我们实际上是取的x,y的引用而不是地址。

 

int
main() {


    vector<int> v;
for
(int
i = 0; i < 10; ++i) {

        v.push_back(i);

    }

int
x = 1;
int
y = 1;


    for_each(v.begin(), v.end(), [&x, &y](int& r) {
const int
old = r;

        r *= x * y;

        x = y;

        y = old;

    });


    for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "
; });

    cout << endl;

    cout << x <<
", "
<< y << endl;

}

 

运行结果如下 -

0 0 0 6 24 60 120 210 336 504

8, 9

 

上面代码会被编译器“翻译”成:

抱歉!评论已关闭.