免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2010 | 回复: 0
打印 上一主题 下一主题

GCC 4.5 中的 C++0x 特性支持 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-01-17 15:39 |只看该作者 |倒序浏览
简介
编译 C++0x 代码[size=0.76em]
只能通过使用 g++ 编译器的 –std=c++0x–std=gnu++0x 命令行选项编译 C++0x 代码。


GNU Compiler Collection (GCC) 是大多数人使用的 C++ 编译器,它已经率先支持即将发布的 C++0x 标准中的特性。本文主要讨论 GCC 4.5 支持的几个 C++0x 特性,包括静态断言、初始化器(initializer)列表、类型窄化(type narrowing)、auto 关键字的新语义、lambda 函数和可变参数模板。其中一些特性(比如静态断言)是在 GCC 4.3 中首次出现的,而 lambda 函数是在 GCC 4.5 中首次出现的。如果希望及早熟悉 C++0x,可以考虑找一份标准草案,下载 GCC 4.5。

静态断言
有时候,可移植的代码在客户站点上崩溃了,因为整数的大小并不是您假设的 4 字节。C++0x 中的 static_assert 构造可以准确地跟踪这类问题,可以在编译时使用这种构造,当需要把源代码迁移到其他平台时非常有用。Boost 库支持静态断言已经有一段时间了,但是静态断言成为语言核心的一部分(static_assertC++0x 关键字)意味着不再需要头文件(见清单1)。

清单 1. 学习使用静态断言
                                // no headers// using static assertion in global scopestatic_assert(sizeof(int) == 4, "Integer sizes expected to be 4");int main(){    return 0;}

在我的 64 位企业 Linux® 系统上,这个断言在编译时失败。下面是日志:
g++ 1.cpp --std=c++0x 1.cpp :1:1: error: static assertion failed: " Integer sizes expected to be 4"

可以在全局范围以及在名称空间、函数体或类声明内使用静态断言。在清单2 中,使用静态断言有助于防止用某些参数类型实例化一个模板化的类。方法是添加一个在特殊化的类版本中一定会失败的断言。一定要记住,在使用静态断言时,检查的表达式在编译时必须是可计算的。

清单 2. 在类声明内使用静态断言
                                // using static assertions as part of class declaration template <typename T1, typename T2>class T {        T1 a;        T2 b;};template <typename T1>class T<T1, bool> {         static_assert(sizeof(T1) == 0, "T<T1, bool> is not allowed, sorry");};int main( ){    T<float, bool> f1; // this will fail compilation    T <float, int> i2; // fine   …}

引入新的字符类型
坦白地说,C++ 对 Unicode 的支持一直不够。Unicode 用三种不同的大小定义字符编码 — UTF-8、UTF-16 和 UTF-32 — 而传统的字符类型是 8 位的。使用 wchar_t 是不可行的,因为 wchar_t 的大小由实现决定。
C++0x 引入了两个新的关键字 — char16_tchar32_t,它们具有确定的大小(分别是 16 和 32 位),都是 unsigned 型的。要想使用这些新类型,必须包含 C++ 头文件 cstdint,尽管标准要求包含头文件 cuchar

清单 3. 使用 char16_t 和 char32_t 数据类型
                                #include <iostream>#include <cstdint>  // this is the header for char16_t and char32_t using namespace std;int main(){    char16_t c = 'A';    cout << sizeof(c) << endl;    char32_t d = 'b';    cout << sizeof(d) << endl;    return 0;}

输出肯定会在控制台上显示 24。现在,试试定义一个 char32_t 类型的数组。

清单 4. 声明并初始化 char32_t 数组
                                #include <iostream>#include <cstdintglt;using namespace std;int main(){    char32_t e[ ] = "Hello World";    cout << sizeof(e) << endl;    return 0;}

下面是编译器日志:
g++ 1.cpp --std=c++0x 1.cpp: In function 'int main()':1.cpp:7:8: error: int-array initialized from non-wide string

这里出了什么问题?对于初学者来说,"Hello World" 是每个字符 8 位的常规字符串,把它赋值给每个字符 32 位的数组是非法的。根据类型在字符串前面加上 uU,就可以创建这些新类型的字符串直接值。

清单 5. 初始化 char16_t 和 char32_t 字符串直接值
                                #include <iostream>#include <cstdint>  using namespace std;int main(){    char16_t f[ ] = u"Hello World";  // prefix the string with u for char16_t    cout << sizeof(f) << endl;    char32_t e[ ] = U"Hello World"; // prefix the string with U for char32_t    cout << sizeof(e) << endl;    return 0; }

控制台输出应该是 2448,g++ 可以顺利地编译此代码。
新的 auto 语法:根据初始化器(initializer)表达式推导类型
编译器能够根据变量的初始化器(initializer)表达式推导出正确的变量类型,这个特性可以节省时间。因此,现在不再需要class1::class3::struct1::enum2 e 这样的声明了。C++0x 为 auto 关键字定义了完全不同的语义。给出第一个示例。

清单 6. 使用 auto 进行自动类型推导
                                #include <iostream>#include <map>  using namespace std;int main(){    auto *num1 = new int(7);  // type for num1 is int*     const auto num2 = 3.1415;  // type for num2 is double    // now for some serious business     map<int, string> map1;    map1.insert(make_pair<int, string> (7, "8"));    auto mapit1 = map1.find(7);  // type for mapit1 is std::map::iterator    cout << mapit1->second << endl;     … // continue coding }

num1 的类型是整数指针;num2 是一个浮点值,mapit1std::map::iteratorauto 支持多个声明,但是所有推导的结果必须是相同的类型。按从左到右的次序处理声明。给出的代码使用 auto,但是不起作用。

清单 7. 错误地使用 auto 关键字
                                int main( ){   auto i = 9, j = 8.2; // error – i and j should be same type   auto k = &k; // dumb error – can’t declare and use in initializer   …}

下面是编译器日志:
1.cpp: In function 'int main()':1.cpp:3:8: error: inconsistent deduction for 'auto': 'int' and then 'double'1.cpp:4:8: error: variable 'auto k' with 'auto' type used in its own initializer

对于 auto,还有其他一些注意事项。例如,C++0x 不允许使用它作为存储类指示符。如果从 g++ 命令行中删除 –std=c++0x,下面的代码片段可以顺利地编译:
auto int variable1 = 8;

下面是 g++ --std=c++0x 日志:
1.cpp: In function 'int main()':1.cpp:28:14: error: two or more data types in declaration of 'variable1'
初始化器列表和类型窄化(type narrowing)
还记得传统 C/C++ 中的 int integer_array1 [ ] = {1, 2, 3, 4, 5}; 构造吗?这个列表是使用 {} 定义的,它是初始化器列表。但是,语言并没有为更广泛地使用这个构造提供语义。C++0x 为初始化器列表定义了更多使用规则:
  • 可以在变量定义中使用初始化器列表
  • 可以在 new 表达式中使用初始化器列表
  • 可以用作函数参数和/或函数返回语句
  • 允许作为下标表达式
  • 允许作为构造器调用的参数
  • 不允许类型窄化
在研究初始化器列表示例和典型用法之前,先讨论一下什么是类型窄化

清单 8. 用浮点值初始化整数数组
                                int main( ){   int nasty[ ] = {8, 99, 2.3, 4.0, 5};   // …    return 0;}

尽管发生了非常糟糕的 doubleinteger 类型转换,但是当用 g++ –Wall 选项编译以上代码时,编译器不会发出任何警告。这就是类型窄化的示例,好在 C++0x 标准不允许这么做。下面是用 g++ –std=c++0x 编译代码时的日志:
1.cpp: In function 'int main()':1.cpp:14:34: error: narrowing conversion of     '2.29999999999999982236431605997495353221893310547e+0'     from 'double' to 'int' inside { }1.cpp:14:34: error: narrowing conversion of '4.0e+0'     from 'double' to 'int' inside { }

现在讨论初始化器列表的用法。

清单 9. 对 STL 容器使用初始化器列表
                                // Initializer list used with variable definitionstd::vector<double> doubles = {2.3, 4.511, 1.23, 0.99};// Initializer list used with new std::list<double> *d2 = new std::list<double> {1.2, 1.3}; // Initialize a map std::map<string, int> = { {“key1”, 1}, {“key2”, 2} };

可以使用初始化器列表对标量变量进行初始化,在这种情况下应用一般的类型窄化规则。如果用空的初始化器列表对变量进行初始化,对象为初始化值。输出是 2 0 a A both x3 and x4 are null

清单 10. 使用初始化器列表进行标量初始化
                                int main( ){    int x{2};    double x2{};    char* x3{};    int* x4 = {};    char c1 = {‘a’} ;    char c2 = char{‘A’} ;    cout << x << " " << x2 << " "          << c1 << " " << c2 << endl;    if (x3 == NULL && x4 == NULL)        cout << "both x3 and x4 are null\n";    // int y{2.3}; → don’t try this error due to type narrowing     return 0;}

注意,在 C++0x 中允许 int y(2.3);由于不允许类型窄化,y 等于 2,而 int y{2.3} 是错的。由于采用这种语义,应该尽可能使用初始化器列表。C++0x 标准定义了一个名为 initializer_list 的类类型,可以使用它把参数传递给函数和构造器。另外,函数返回语句可以返回 initializer_list;但是,必须包含头文件 initializer_list 才能使用这个类类型。给出 initializer_list 的使用示例。

清单 11. 使用初始化器列表作为函数参数和返回类型
                                #include <initializer_list> // argument to functionvoid func1(std::initializer_list<int>);// function returning initializer liststd::initializer_list<double> func2 (double);

说明如何访问初始化器列表。注意,函数只能作为不可变的序列访问这个列表 — 也就是说,尝试修改列表的内容会导致错误。

清单 12. 访问初始化器列表的内容
                                #include <initializer_list> using namespace std; void display (initializer_list<int> arguments) {    for (auto p= arguments.begin(); p!= arguments.end(); ++p) {            // *p = *p * 2; → Not allowed to modify data             cout << *p << "\n";    }}int main( ){    display( {3, 77, 8, 1, 9} );    return 0;}
对初始化器列表使用 auto
x1 的类型是什么?

清单 13. 对初始化器列表使用自动类型推导
                                int main( ){    auto x1 = {2, 4};    auto z2 = {3, 2.3} ; // go figure    ...    return 0 ;}

x1 的类型是 std::initializer_list<int>。对于 z2,g++ 会抛出错误,指出不允许类型窄化。如果您仍然不相信 x1 的类型,就看看清单 14

清单 14. 对初始化器列表使用自动类型推导
                                #include <iostream>#include <initializer_list>using namespace std;template <typename T>void display() {}template <>void display<std::initializer_list<int>> () { cout << "Hurray!\n"; }int main(){   auto x = {2, 3};   display<decltype(x)> ();   return 0;}

输出是 Hurray!display 模板特化结束了所谓的 x1 类型争论。这还引出了另一个 C++0x 构造:decltype
了解 decltype
C++ 没有提供查询变量或表达式的类型的简便方法。GCC 提供一个名为 typeof 的扩展,但它不是标准的。对于C++0x,输入 decltype 操作符就会返回变量或表达式的类型。如果您熟悉模板编程,这个特性很可能会促使您马上使用 C++0x。对一个变量应用 decltype 操作符,演示对表达式使用这个操作符的方法。

清单 15. 对表达式应用 decltype 操作符
                                T1 x; T2 y;typedef T3 decltype(x+y); T3 z ;
lambda 函数
如果要给 C++0x 的新特性评奖的话,得头奖的应该是 lambda 函数。lambda 函数 是匿名的函数,这意味着不必定义典型的 C/C++ 函数也能够完成工作。lambda 函数最常用的地方可能是 STL sort。到目前为止,要想使用定制的比较函数,标准的做法是定义自己的函数对象,然后适当地定义操作符 ( )。请考虑一个包含 5 个字符串的向量;希望按字符串长度的升序排序。给出过去的做法。

清单 16. 过去的排序方法
                                #include <iostream>#include <string>#include <vector>#include <algorithm>using namespace std;struct compare {    bool operator()(const string& s1, const string& s2) {         return s1.size() < s2.size();    }};int main(){    vector<string> vs = {"This", "is", "a", "C++0x", "exercise"};    std::sort(vs.begin(), vs.end(), compare());    for (auto ivs = vs.begin(); ivs != vs.end(); ++ivs)        cout << *ivs << endl;    return 0;}

给出使用 lambda 函数的新方法。

清单 17. 定义用于定制排序的 lambda 函数
                                #include <iostream>#include <string>#include <vector>#include <algorithm>using namespace std;int main(){    vector<string> vs = {"This", "is", "a", "C++0x", "exercise"};    std::sort(vs.begin(), vs.end(),        [ ](const string& s1, const string& s2) {           return s1.size() < s2.size();        }     )    for (auto ivs = vs.begin(); ivs != vs.end(); ++ivs)        cout << *ivs << endl;    return 0;}

这两个程序输出都是 a is This C++0x exercise。 说明如何定义 lambda 函数。它看起来与定义常规函数的代码很相似,除了 [ ] 部分,对吗?对也不对。在深入讨论之前您必须问几个问题:
  • lambda 函数的返回类型怎么处理?
  • 这些函数可以访问在它的范围之外定义的变量吗?例如,lambda 函数是否可以访问 vs
不需要为 lambda 函数提供返回类型;根据返回语句推导出返回类型。lambda 函数的返回类型是 Boolean。对于第二个问题,可以通过中的代码来解答。

清单 18. 在 lambda 函数内访问范围外的变量
                                int main(){    vector<string> vs = {"This", "is", "a", "C++0x", "exercise"};    std::sort(vs.begin(), vs.end(),        [ ](const string& s1, const string& s2) {           cout << vs.size( ) << endl;          return s1.size() < s2.size();        }     )   …}

每当 sort 调用这个 lambda 函数时,试图输出向量的大小。下面是编译时 g++ 报告的消息:
1.cpp: In lambda function:1.cpp:12:9: error: 'vs' is not captured

“纠正” 这个问题有两种方法 — 实际上是四种方法。可以使用前面的方括号 ([]) 把参数传递给 lambda 函数。如果选择通过引用传递变量,就在它前面加上 & 并把它放在 [ ] 内;对于通过值传递的变量,在它前面加上等号 (=)。通过引用传递 vs

清单 19. 通过引用把变量传递给 lambda 函数
                                int main(){    vector<string> vs = {"This", "is", "a", "C++0x", "exercise"};    std::sort(vs.begin(), vs.end(),        [ &vs ](const string& s1, const string& s2) {           cout << vs.size( ) << endl;          return s1.size() < s2.size();        }     )   …}

现在,编译器会顺利地完成编译。(请用 [ =vs ] 试试。)那么,把向量传递给 lambda 函数的第三种方法是什么?使用 [ & ]。这种语法表示把所有局部变量通过引用传递给 lambda 函数。从技术上说,还可以使用 [ = ] 把所有局部变量通过值传递给函数,这是第四种方法。但是,由于性能的原因,不推荐这种方法。
可变参数模板
如何定义具有数量可变的参数(每个参数的类型可能不同)的模板化类或函数?C 支持使用 va_list 定义具有数量可变的参数的函数,C++ 在这方面没有改进。直到现在,一直是这样。C++0x 允许定义具有数量可变的参数的函数或类,现在 GCC 提供相同的支持。给出语法。

清单 20. 可变参数函数和类模板
                                template<typename... Types> void f(Types... args) // variable number of function arguments {}template<typename... Types> class c // class with {   // member code };// Usagesf(‘a’, “hello”, 2, 3.1); class c<int, double, std::vector<string>> c1;

可以使用 typename… 声明可变参数模板。C++0x 还定义了 sizeof… 操作符,它显示参数数量。不幸的是,无法直接遍历参数。探索模板的惟一方法是定义同样的递归版本以及基本案例。这并不理想,但是目前只能这样做。演示如何输出可变参数函数模板的参数。

清单 21. 显示可变参数函数模板的内容
                                void f() { }template<typename T, typename... Types> void f(T a, Types... args){    cout << sizeof...(args) << endl;    cout << a << endl;    f(args...);}int main(){   f("hello", 'a', 8.333);   return 0;}

下面是发生的情况:
  • 首先调用 f 的模板化版本,第一个参数的类型是字符串,作为大小可变的参数提供字符和双精度值。
  • 对于第一次调用,sizeof…(args) 显示 2,因为可变的 args 列表中只有两个参数。
  • 观察语法 args… 现在在 args 的右边。对 f 的第二次调用的可变列表只包含 8.333,而 a 变成第一个参数。
  • 当处理完所有参数时,g++ 调用 void f() { },这是递归的基线条件。
下面输出:
2hello1a08.333

未来的改进
未来的 GCC 版本可能会更让人兴奋。可能会出现的新特性包括:
  • 垃圾收集应用程序二进制接口 (ABI) 支持。应用程序开发人员可以通过 C++0x 标准定义的接口调整垃圾收集过程。例如,预定义函数 void declare_reachable(void* p); 要求垃圾收集器不回收指针 p 指向的存储。
  • 更好地支持并发。GCC 对 C++0x 线程的支持并不适用于所有平台(例如 Cygwin)。正在开发与线程相关的存储。

结束语
对于 C++(或者应该说 C++0x)开发人员,这是一个让人兴奋的时期。标准委员会添加的特性可以改进 C++ 的模板编程、类型安全性、系统软件和库开发本文简要介绍了现在在 GCC 4.5 中支持的几个语言特性。

关于作者
Arpan Sen 是致力于电子设计自动化行业的软件开发首席工程师。他使用各种 UNIX 版本(包括 Solaris、SunOS、HP-UX 和 IRIX)以及 Linux 和 Microsoft Windows 已经多年。他热衷于各种软件性能优化技术、图论和并行计算。Arpan 获得了软件系统硕士学位。


您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP