译文

VC10中的C++0x特性 Part 1 (1) : Lambdas,auto,以及 static_assert

翻译:飘飘白云 | 2009-05-28 12:02:15 | 阅读591 | 来源

VC10中的C++0x特性 Part 1 (1) : Lambdas,auto,以及 static_assert

 

来源:vcblog 翻译:飘飘白云 kesalin@gmail.com

 

共三页: 本页 第二页 第三页

 

Microsoft Visual Studio 2010 九月社区技术预览版 (CTP)所带的Visual C++编译器对四个C++0x语言特性提供了支持,也就是 lambdasautostatic_assert,以及 rvalue references (右值引用,译注:后面不再对这个词进行翻译)。今天,我将详细介绍前三个特性。(很快我将贡献一整篇幅的文章来解释右值引用,仅仅是因为再在这里解释的话将会加大这篇已经很长的文章的篇幅)

首先,说明一些事情:


1,今天的这篇文章是由 Stephan T. Lavavej,Visual C++库的开发人员以及C, A, 与 T读者投书栏带给你们的。注意作为库的开发人员,我并没有实现这些特性。那是 Jonathan Caves,前端编译器开发者,选举标准委员会成员以及所有“忍者”(鲜为人知的高手)的成果。


2,我将 Visual C++ compiler in VS 2010 简称为 VC10 ( VS 2008 包含 VC9,VS 2005 包含 VC8,等等。 - 10 并不比 2010 简短)


3,C++0x 指的是即将到来的 C++ 标准,现在还在起草中。(C++标准委员会希望它可以在 2009 年完成,称作 C++ 09;玩笑话说如果它推迟到 2010 或者更晚的话,“x” 将是十六进制的了)。 C++ 98 和C++ 03 指的是当前的 C++ 标准。(在这里不回顾历史了, C++ 标准 2003 仅仅是最初的 C++ 1998 标准 的“补丁”版,对大部分人来说可以忽略两者间的区别。C++ 03 和 C++ 0x 模样虽然看起来差不多,但完全不同)


4,我要感谢标准委员会开发出这些奇妙而有用并富有艺术的特性。他们也在以下站点上提供了重要的文档:


C++0x 语言特性:

http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html

C++0x 库特性:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html

C++0x 进行中的草案:

http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf


5,总是会有bug的(虽然希望不会太多),这也就是发布 CTP 版本的主要目的(让用户测试发现 bug )。请通过 Microfsoft 把这些 bug 报告给我们。


现在,让我们来审视这些特性吧!

 

lambdas


在 C++ 0x 中,“lambda 表达式”隐式定义并构建不具名函数对象,这些对象就像手写函数对象一样。下面是 lambda “Hello,World”入门级的示例:


C:Temp>type 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;

}

 

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

0 1 2 3 4 5 6 7 8 9


[] 操作符是 lambda 导引符, 它告诉编译器一个 lambda 表达式开始了。 (int n) 是 lambda 参数声明,它告诉编译器不具名函数对象类的函数调用操作符带有哪些参数, { cout << n << " "; }  是复合声明,它是不具名函数对象类函数调用操作的函数体。不具名函数对象类函数调用操作默认返回 void


这样,C++0x 在内部将它转换成如你在C++ 98 下编写的一样代码:

 

C:Temp>type meow98.cpp

#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;

}

 

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

0 1 2 3 4 5 6 7 8 9

 

现在我将不再累述类似“不具名函数对象类函数调用操作符默认返回 void”这样的话,开始换用“lambda 函数返回 void”的说法,但是记住 lambda 表达式做了些什么是很重要的,那就是:定义类并构建对象。


当然,lambda 的复合声明部分(函数体部分)可以包含多个声明语句,譬如:


C:Temp>type multimeow.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;

 

        if (n % 2 == 0) {

            cout << " even ";

        } else {

            cout << " odd ";

        }

    });

 

    cout << endl;

}

 

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

0 even 1 odd 2 even 3 odd 4 even 5 odd 6 even 7 odd 8 even 9 odd

 

lambda 函数也并不总是必须返回 void。如果 lambda 的复合声明语句像是这样的 { return expression; } ,那么 lambda 的返回类型就会自动地被推断成 expression 的类型


C:Temp>type cubicmeow.cpp

#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;

}

 

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

729 512 343 216 125 64 27 8 1 0

 

在这里,  n * n * n 的类型是 int,所以 lambda 函数返回 int


有着复杂复合声明语句的 lambda 函数不会自动推断返回类型,你必须显式指定返回类型。


C:Temp>type returnmeow.cpp

#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<double> d;

 

    transform(v.begin(), v.end(), front_inserter(d), [](int n) -> double {

        if (n % 2 == 0) {

            return n * n * n;

        } else {

            return n / 2.0;

        }

    });

 

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

    cout << endl;

}

 

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

4.5 512 3.5 216 2.5 64 1.5 8 0.5 0

 

-> double 是可选的 lambda 返回类型从句。为什么它不放在左边(译注:返回类型一般在函数左边声明),就像程序员一直以来在C函数中做的那样?因为那样的话 lambda 导引符 [] 就不会第一个出现了,而正是它告诉编译器一个 lambda 函数开始了。(核心工作组最擅长解决这样的问题;尝试猜测C++ 中一个给定的概念是否是可被解析的会让我头疼。)


如果忘记了指定 lambda返回类型从句,编译器就会抱怨每一个返回语句:

 

C:Temp>cl /EHsc /nologo /W4 borkedreturnmeow.cpp

borkedreturnmeow.cpp

borkedreturnmeow.cpp(20) : error C3499: a lambda that has been specified to have a void return type cannot return a value

borkedreturnmeow.cpp(22) : error C3499: a lambda that has been specified to have a void return type cannot return a value



到目前止我所介 lambda 都是无状的:它不包含数据成。你也可以有有状 lambda是通传递(原文用加引号的 capturing 这个词,在这里我翻译成传递似乎不太妥,故我都加括号引用原文,下同)局部量来实现的。空的 lambda 引符 [] 意味着一个无状 lambda”,但在 lambda 引符 [] 中你可以指定 capture-list

 

C:Temp>type capturekittybyvalue.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);

    }

 

    int x = 0;

    int y = 0;

 

    // op>>() leaves newlines on the input stream,

    // which can be extremely confusing. I recommend

    // avoiding it, and instead using non-member

    // getline(cin, str) to read whole lines and

    // then parse them. But in the interests of

    // brevity, I'll use evil op>>():

 

    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;

}

 

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

Input: 4 7

0 1 2 3 4 7 8 9

如果你忘capture-list编译器就会抱怨:

C:Temp>cl /EHsc /nologo /W4 borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp(27) : error C3493: 'x' cannot be implicitly captured as no default capture mode has been specified

borkedcapturekittybyvalue.cpp(27) : error C3493: 'y' cannot be implicitly captured as no default capture mode has been specified


(我很快就会解的传递capture))

记着,lambda 表达式隐式地定义了一个不具名函数对象类。复合声明语句 { return x < n && n < y; } 在这个类中被当作函数调用操作符的函数体。虽然从词法结构上看复合声明语句是在 main() 块之内,但在概念上它是在 main() 块之外的,因此如果不传递capture到 lambda 中去,就不能在其中使用来自main() 中的局部变量。

上面的代码在内部被翻译成:

C:Temp>type capturekittybyvalue98.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

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; // EVIL!

 

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

 

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

    cout << endl;

}

 

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

Input: 4 7

0 1 2 3 4 7 8 9


在这里你可以清楚地看到“按值”传递(captures)的。函数对象存储了局部变量的拷贝。这就使得函数对象可以比通过传递(capture)来创建它们的局部变量有更长的生命期。但是,要注意:(a)在 lambda 中不能修改通过传递(capture)获得的拷贝,因为默认情况下函数调用操作符是 const 属性的,(b)一些对象的拷贝开销是昂贵的,(c)局部变量的更新不会反应到通过传递(capture)获得的拷贝(在语义上它们是原始值)。很快我就会解释如有需要应该如何来处理以上情况。


但是首先,你可以“按值传递(capture)任何东西”,而不用特别指明每一个你想要传递(capture)的局部变量。其语法是使用这种形式的 lambda 导引符 [=] (默认传递(capture-default= 应该可以让你想起赋值或者拷贝初始化 Foo foo = bar; 虽然这里的拷贝实际上是直接初始化(通过初始化列表进行赋值),就像上面的 m_a(a))。


C:Temp>type defaultcapturekittybyvalue.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);

    }

 

    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;

}

 

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

Input: 4 7

0 1 2 3 4 7 8 9


当编译器看到 lambda 中的 x 和 y, 就会从 main() 中按值传递(capture)。

 

 

< 本页   下页   下下一页 >

 

【本文翻译仅为外语学习及阅读目的,原文作者个人观点与译者及译言网无关】

分享:

标签:C++0x,

本文共有3 条评论:

1楼 Maximilian.μ 评论于 2009-05-29 17:23:54

分类应该属于“电脑网络”吧

2楼 飘飘白云 评论于 2009-05-29 20:16:00

好的,我把分类改下,我原以为译言上电脑网络是关于软件使用以及it资讯方面的,而软件开发相关的有个专业就叫计算机科学与技术,所以就归档到科学技术里面来了~~

3楼 Maximilian.μ 评论于 2009-05-29 20:47:14

@飘飘白云

其实严格来说这类文章属于科学技术 只是计算机类的文章太多 所以单独分了出来 便于浏览

当然,个人觉得“电脑网络”这个分类名称不甚恰当

添加评论