VS2010 中的 C++ 0x 新特性:Lambdas、auto 和 static_assert

来源:互联网 发布:wp8怎么下载软件 编辑:程序博客网 时间:2024/05/15 21:59

今天在看c++的时候,找到这些文章,很是有用,所以转到自己的空间,希望你看到也能够帮助你。

原文地址:http://www.cppblog.com/flyinghare/archive/2011/02/18/140244.html


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

Visual Studio 2010 中的 Visual C++ 编译器,即 VC10, 包含了 4 个 C++ 0x 的语言特性:lambda 表达式,自动类型推演(auto 关键字),静态断言(static_assert)和右值引用(rvalue reference)。

Lambda 表达式

使用过函数式编程语言(如 LISP、 F#)或一些动态语言(如 Python、Javascript)的大侠对于 lambda 表达式一定不会陌生。在 C++ 0x 中,引入了 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 << " "; });            cout << endl;                         return 0;            }


运行结果如下:

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。

为了方便,以下会用“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;                         return 0;            }


上例中返回值 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 都是无状态的,不包含任何数据成员。很多时候我们需要 lambda 包含数据成员以保存状态,这一点可以通过“捕获”局部变量来实现。

Lambda 表达式的导入符(lambda introducer)是空的,也就是“[]”,表明该 lambda 是一个无状态的。但是在 lambda导入符中可以指定一个“捕获列表”,下面的代码中的 lambda 使用了局部变量 x 和 y,将值介于 x 和 y 之间的元素从集合中删除:

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;                         return 0;            }


运行结果如下:

Input: 4 70 1 2 3 4 7 8 9

上面代码中很重要的一点信息是: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 将捕获所有的局部变量,当然也是传值方式。这种方式姑且被称为“缺省捕获”:

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;                         return 0;            }


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

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

第 1 点,修改 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;                         return 0;            }


运行结果如下:

0 0 0 6 24 60 120 210 336 5041, 1

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

  1. 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;                         return 0;            }


运行结果如下:

0 0 0 6 24 60 120 210 336 5048, 9

注意:当你使用 lambda 时,VC10 编译器会为 lambda 的定义部分自动禁用 C4512 警告。

当以传引用方式捕获局部变量时,lambda 的函数对象在自己内部以引用方式保存 main 函数中的局部变量。当然因为使用的是局部对象的引用,使用lambda表达式时一定要注意不能够超出局部变量的生命周期。

和上文提高的[=]类似,我们可以用[&]来以“传引用”的方式捕获所有的局部变量。

到目前为止,局部变量的捕获方式要么是“值语义”要么是“引用语义”,那么可以混合这两种方式吗?可以!例如:[a, b, c, &d, e, &f, g],其中变量 d 和 f 是按引用语义捕获,而 a、b、c、e 和 g 是按值语义捕获。

另外很有用的一点是:你可以指定一个缺省捕获,然后重载某些局部变量的捕获方式。下边例子中[=, &sum, &product] 告诉编译器用值语义方式捕获所有的局部变量,但是有两个例外 - sum和product是按引用语义来捕获。

int main() {                         vector<int> v;            for (int i = 0; i < 10; ++i) {            v.push_back(i);            }                         int sum = 0;            int product = 1;            int x = 1;            int y = 1;                         for_each(v.begin(), v.end(), [=, &sum, &product](int& r) mutable {            sum += r;                         if (r != 0) {            product *= 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 << "sum: " << sum << ", product: " << product << endl;            cout << "x: " << x << ", y: " << y << endl;                         return 0;            }


运行结果如下:

0 0 0 6 24 60 120 210 336 504sum: 45, product: 362880x: 1, y: 1

再来看看下边的代码,在lambda中使用类成员变量:

class Kitty {                         public:            explicit Kitty(int toys) : m_toys(toys) {}                         void meow(const vector<int>& v) const {            for_each(v.begin(), v.end(), [m_toys](int n) {            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;            });            }                         private:            int m_toys;            };                         int main() {            vector<int> v;            for (int i = 0; i < 3; ++i) {            v.push_back(i);            }                         Kitty k(5);            k.meow(v);                         return 0;            }


不幸的是,编译这段代码将产生这样的错误:

error C3480: 'Kitty::m_toys': a lambda capture variable must be from an enclosing function scope

为什么呢?lambda表达式能够让你不活局部变量,但是类的数据成员并不是局部变量。解决方案呢?别着急。lambda 为捕获类的数据成员大开方便之门,你可以捕获this指针。

class Kitty {            public:            explicit Kitty(int toys) : m_toys(toys) {}            void meow(const vector<int>& v) const {            for_each(v.begin(), v.end(), [this](int n) {            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;            });            }                         private:            int m_toys;            };                         int main() {            vector<int> v;            for (int i = 0; i < 3; ++i) {            v.push_back(i);            }                         Kitty k(5);            k.meow(v);                         return 0;            }


运行结果如下:

If you gave me 0 toys, I would have 5 toys total.If you gave me 1 toys, I would have 6 toys total.If you gave me 2 toys, I would have 7 toys total.

当 lambda 表达式捕获“this”时,编译器看到 m_toys 后会在 this 所指向对象的范围内进行名字查找,m_toys 被隐式地推演为 this->m_toys。当然你也可以让编译器省省力气,显式地在捕获列表中使用 this->m_toys。另外,lambda 比较智能,你也可以隐式地捕获 this 指针,如下所示:

class Kitty {                         public:            explicit Kitty(int toys) : m_toys(toys) { }            void meow(const vector<int>& v) const {            for_each(v.begin(), v.end(), [=](int n) {            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;            });            }                         private:            int m_toys;            };                         int main() {            vector<int> v;            for (int i = 0; i < 3; ++i) {            v.push_back(i);            }                         Kitty k(5);            k.meow(v);            }


运行结果如下:

If you gave me 0 toys, I would have 5 toys total.If you gave me 1 toys, I would have 6 toys total.If you gave me 2 toys, I would have 7 toys total.

注意你也可以在上面代码中用 [&],但是结果是一样的——this 指针永远是按值语义被传递(捕获)的。你也不能够使用 [&this],呵呵。

如果你的 lambda 表达式是没有参数的,那么 lambda 表达式的导入符后边的括号()也可以省掉。例如:

int main() {            vector<int> v;            int i = 0;            generate_n(back_inserter(v), 10, [&] { return i++; });            for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });            cout << endl;            cout << "i: " << i << endl;            }


上边是 [&]() { return i++; }的简写形式,个人认为省掉括号并不是什么好的 coding style。如果你需要用到mutable或者指定lambda的返回类型,空的括号就不能够省略了。

最后,既然 lambda 表达式生成是普通的函数对象,所以函数对象支持的用法 lambda 都支持。例如和 tr1 的 function 一起使用,看看下边的代码,是不是很酷?

using namespace std;            using namespace std::tr1;                         void meow(const vector<int>& v, const function<void (int)>& f) {            for_each(v.begin(), v.end(), f);            cout << endl;            }                         int main() {            vector<int> v;            for (int i = 0; i < 10; ++i) {            v.push_back(i);            }                         meow(v, [](int n) { cout << n << " "; });            meow(v, [](int n) { cout << n * n << " "; });                         function<void (int)> g = [](int n) { cout << n * n * n << " "; };            meow(v, g);                         return 0;            }


运行结果:

0 1 2 3 4 5 6 7 8 90 1 4 9 16 25 36 49 64 810 1 8 27 64 125 216 343 512 729

auto 关键字

auto 这个关键字来自 C++ 98 标准。在 C++ 98 中它没有什么作用,C++ 0x 中“借用”它来作为自动类型推演(automatic type deduction)。当 auto 出现在声明中时,它表示“请用初始化我的表达式类型作为我的类型”,例如下面代码:

#include <iostream>            #include <map>            #include <ostream>            #include <regex>            #include <string>                         using namespace std;            using namespace std::tr1;                         int main() {            map<string, string> m;                         const regex r("(\\w+) (\\w+)");                         for (string s; getline(cin, s); ) {            smatch results;                         if (regex_match(s, results, r)) {            m[results[1]] = results[2];            }            }                         for (auto i = m.begin(); i != m.end(); ++i) {            cout << i->second << " are " << i->first << endl;            }                         return 0;            }


运行结果如下:

cute kittensugly puppiesevil goblins^Zkittens are cutegoblins are evilpuppies are ugly

上面例子中i的类型在编译时推演为 map::iterator, 有了 auto 关键字你再也不用写又长又烦的代码了。(注意 m.begin() 返回类型是 iterator, 而不是 const_iterator, 因为这里的 m 并不是 const。C++0x 中的 cbegin() 能够解决这个问题,它返回 non-const 容器的 const 迭代器。)

Lambda 表达式和 auto 关键字的配合

上文中提到了用 tr1::functions 来存储 lambda 表达式,但是不建议那样做除非不得已,因为 tr1::functions 的开销问题。如果你需要复用 lambda 表达式或者像给它命名,那么 auto 是更好的选择。

#include <algorithm>            #include <iostream>            #include <ostream>            #include <vector>                         using namespace std;                         template <typename T, typename Predicate> void keep_if(vector<T>& v, Predicate pred) {            auto notpred = [&](const T& t) {            return !pred(t);            };                         v.erase(remove_if(v.begin(), v.end(), notpred), v.end());            }                         template <typename Container> void print(const Container& c) {            for_each(c.begin(), c.end(), [](const typename Container::value_type& e) { cout << e << " "; });            cout << endl;            }                         int main() {            vector<int> a;                         for (int i = 0; i < 100; ++i) {            a.push_back(i);            }                         vector<int> b;                         for (int i = 100; i < 200; ++i) {            b.push_back(i);            }                         auto prime = [](const int n) -> bool {            if (n < 2) {            return false;            }                         for (int i = 2; i <= n / i; ++i) {            if (n % i == 0) {            return false;            }            }            return true;            };                         keep_if(a, prime);            keep_if(b, prime);            print(a);            print(b);                         return 0;            }


运行结果如下:

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199

上面代码中 notpred 是一个 lambda 表达式的否定式。这个例子中我们不能够使用 C++ 98 的 not1(),因为 not1 要求你的谓词是从 unary_function 派生的,但是 lambda 并不要求这点,所以很多情况下使用 lambda 更灵活。

静态断言 static_assert

断言(assertion)是提高代码质量的有效武器。C++标准库中的 assert、MFC 中的 ASSERT /VERIFY 宏都是断言的例子,它们的共同点是在运行时对程序状态进行判断,例如检查函数的参数有效性、检查类的不变式等。而 C++ 0x 中的静态断言呢,和运行时的断言不一样,它是编译时执行检查的。看下面的例子:

template <int N> struct Kitten {            static_assert(N < 2, "Kitten<N> requires N < 2.");            };                         int main() {            Kitten<1> peppermint;            Kitten<3> jazz;                         return 0;            }


编译结果如下:

staticfluffykitten.cpp(2) : error C2338: Kitten<N> requires N < 2.staticfluffykitten.cpp(8) : see reference to class template instantiation 'Kitten<N>' being compiledwith[N=3]

上面例子中用 static_assert 对模板参数 N 进行了检查,如果断言失败编译器将使用用户自定义的错误消息。

转自:http://wutiam.net/2010/06/lambdas-auto-and-static-assert-c-0x-features-in-vc10/

原创粉丝点击