C++0x(C++11)新特性点评

右值引用和移动构造语义

说明

移动构造(move constructor)使用移动而非复制语义完成构造过程,主要用于解决从函数返回值时的大量深拷贝开销。使用右值引用和移动构造,在按值传递和返回临时对象时,即可免去不必要的内存分配和数据拷贝开销。

优点

  • 在特定情况下,可实现内存零拷贝,从而大大提高执行效率。

缺点

  • 进一步复杂化了对象传递的语义。可想而知,复合使用了传址(指针、引用、右值引用,和它们的各种常量变体)以及传值等各种对象传递方法的代码,其学习和维护成本都将较高。并且无论对其使用者还是实现者来说,都比较容易产生 bug。
  • 需要为每个对象考虑多实现一种新的构造方法。
  • 在传统的 C++03 代码中,通过智能指针等手段完全可以实现同样的零拷贝。若使用了带有引用计数/写时拷贝等特性的代理实现,还能够达到更好的零拷贝效果和更优的性能。
  • 通过智能指针等手段达到零拷贝的目的,其方便性和易用性至少能够与右值引用+移动构造相当。

总结

只有在实现标准库等某些必须返回值(而不能返回智能指针等句柄对象,也不能返回引用)的场合,才考虑使用右值引用+移动构造方案。其它情况下,智能指针或代理类通常是更好的选择。

常量表达式(constexpr)

说明

constexpr 主要用于修饰函数返回值,表明该函数的返回值是编译时已知的,意即:此函数内仅包含一条形如“return 常量表达式;”这样的语句。constexpr 同样也可以用来修饰对象,如果用它来修饰用户自定义类型,则用户需要为该类型配备 constexpr 构造函数,该构造函数同样仅允许使用编译时已知的常量完成成员初始化。

优点

  • 比内联还让函数像宏。

缺点

  • 还不如宏能做的事情多。

总结

意思不大。

放宽的 POD 类型定义

说明

POD(Plain Old Data)类型指那些布局与 C struct 兼容的用户自定义类型。C++03 中对 POD 类型的定义过于严苛,在新标准中,基本上,只要一个类不使用虚表和虚基类;只使用默认的构造和析构函数,并且其所有基类和成员也都如此的话,这个类的布局就是 POD 兼容的。新标准取消了所有成员都必须是 public 访问权限等没有意义的限制条件。

优点

  • 无。

缺点

  • 无。

总结

事实上,貌似从 C++ 诞生开始,广大编译器厂商就一直在使用上述放松标准了。因此这一改动只不过是顺应事实之举,对现有 C++ 程序员来说没有任何影响。

extern 模板修饰

说明

通过使用 extern 来修饰一个模板类,告知编译器该模板无需在当前编译单元内实例化(必要的实例化已在其它编译单元内进行)。

优点

  • 提供对于程序员来说,更明确,与类型实例化(外部变量和对象定义)看上去更相似的模板显式实例化方式。

缺点

  • 无。

总结

大部分近代 C++ 编译器都已支持模板的显式实例化。甚至不少编译也早已允许在模板显式实例化语句前面加个“extern”修饰词了。因此跟上一条一样,这也是属于“先上车后补票”的勾当。

初始化列表

说明

允许非 POD 容器类型通过初始化列表完成构造。

优点

  • 对于很多容器类型来说(比如:std:vector),可大大方便其创建过程。

缺点

  • 需要添加额外的构造函数。

总结

好用的功能,很适合经常需要通过初始化列表来构造容器对象的场合。

统一的初始化方式

说明

无论是 POD 还是非 POD 类型都可以使用 obj = { ... } 的方式来进行初始化。对于非 POD 类型 { ... } 将自动匹配和调用构造函数。因此,构造函数 T(x, y) 现在也可以写成:T{x, y}、T var{x, y} 等。在需要返回一个 T 类型对象时,甚至可以直接写:“return {x, y};”

优点

  • 一致的构造方式便于编译器和辅助工具完成语法解析。
  • 可以灵活地构造和创建对象。

缺点

  • 滥用类似“return {x, y};”形式的自动类型推断很容易降低代码易读性和可维护性。

总结

分场合酌情使用。

类型推断

说明

在 C++11 中,auto 关键字的语义有所更改,从原来的声明一个存活于当前调用栈上的自动变量,转变成了通过表达式的右值来自动推断左值的类型。decltype 关键字则用于判断指定表达式的类型。

优点

  • 在定义模板,或需要获得 Lambda 表达式等匿名对象的类型时,可提供方便。

缺点

  • 在模板中,应只在类型参数未知或很难获得的时候才应考虑使用上述机制。否则很容易极大地降低代码易读性和可维护性。
  • 类似地,在普通代码中,只应在操作匿名类型对象时,才使用上述机制。
  • 虽然在 C++03 中,auto 关键字很少用到。但也不可避免的带来了兼容性问题——很少用到毕竟不等于完全没用到。

总结

应仅在“被逼无奈”时使用。

基于范围的遍历操作

说明

没啥好多说的,就是现如今已快烂大街的 foreach 类操作了。对一些特殊容器和大部分守规矩(支持迭代子和 begin()、end() 调用)的容器类型都有效。

优点

  • 方便完成容器遍历。

缺点

  • 无。

总结

随大流的 for 语句便捷写法。没有它日子也能过,有了确实会方便些,但也程度有限。

Lambda 表达式和闭包

说明

相当于:

因此可以认为 lambda 表达式和闭包是一种生成匿名函数对象的快捷方式。

优点

  • 大大简化了函数对象的生成,非常适合于用作 STL 算法的谓词。由于 STL 算法中的大部分都是类似 std::for_each 这种的简单算法,如果没有 Lambda 表达式和闭包的话,如上例所示,为其定义谓词的代价过于高昂,以至于直接使用迭代子和 for 语句完成遍历反而更便捷。
  • 可以直接编写某些简单运算的函数对象临时实例,例如:“auto foFunc = [](int x, int y) -> int { int z = x + y; return z; }”,使得函数对象更便于使用。
  • 由于 lambda 表达式生成一个无名类型的实例,因此即使在头文件的内联函数中也可以使用函数对象,不用担心将该文件 include 到多个编译单元时发生编译错误。

  • 使用闭包的描述方式更容易被编译器优化。

缺点

  • 仅适用于封装只有几行代码的简单函数对象。如果滥用则很容易极大地降低代码易读性和可维护性。

总结

经典的特性,如果使用恰当,能发挥强大的威力。

返回值后置式函数声明语法

说明

template<class Lhs, class Rhs>

autoadding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}

显而易见,这种语法主要用来帮助完成模板定义时的类型推断——在上例中,由于用来推断返回类型的“lhs”和“rhs”在“adding_func(...)”之前还未定义,因此必须将返回值类型定义延迟到参数列表之后。

应注意到,使用这种后置语法时,函数原来指定返回值类型的位置必须记做“auto”。

优点

  • 方便一些特殊情况下的返回值定义。

缺点

  • 如果滥用则容易降低代码易读性和可维护性。

总结

要么在整个项目中一致地使用,要么仅作为一种解决特定问题的手段,在“被逼无奈”时才使用。

委托和成员默认初始值

说明

在 C++11 中,一个构造函数可以调用该类中的其它构造函数来完成部分初始化任务(委托)。声明成员时可以直接指定默认初始值,例如:

class CSomeType {

int m_nNumber;

int m_nValue = 10 ;

public:

CSomeType(int new_number) : m_nNumber(new_number) {}

CSomeType() : CSomeType(42) {}

};

优点

  • 可在一定程度上简化初始化相关代码。
  • 比起将构造的公共部分封装为一个私有方法供所有构造函数调用,委托更便于编译器实现一些优化。

缺点

  • 在 C++03 中,如果多个构造函数需要使用同一段初始化代码,亦可将这段代码封装为一个私有方法供所有构造方法调用。
  • 委托的加入改变了构造的语义:C++03 中,当一个对象的构造函数返回时,该对象构造完成;而在新语义中,仅当一个对象的所有构造函数调用都返回后,才表明该对象构造完成,使得对象构造过程被进一步地复杂化。
  • 类似地,基类和成员的构造语义也被相应地复杂化:仅当基类中的所有构造函数调用均返回时,派生类成员才开始构造;当成员中的所有构造函数均返回后,派生类才开始构造……

总结

利大于弊,值得一试。但要搞清楚新的语义,还要考虑好构造函数中的异常抛出与处理方式。

override 和 final 修饰符

说明

final 修饰符可用于修饰类或方法。在修饰类时,它表示指定类是一个 concrete 类——不能再作为基类而被其它类继承。

将 final 修饰符作用于方法时,表示此虚方法已经是最终实现,任何在派生类中重载这个方法的企图都将引发一个编译错误。

优点

  • 定义和派生抽象类的利器,可在编译时发现诸如:修改了基类中某虚方法的参数列表后,忘记在其派生类中做出相应修改等各类相关错误。
  • 使得类和虚函数定义的语义更清晰,有利于增强可读性。
  • 为编译器提供了更多的优化线索。
  • override 和 final 只是修饰符,不是关键字。它们除了在特定上下文中有特别含义外,仍然可以作为合法的标识符使用。

缺点

  • 无。

总结

居家旅行,外出打鸟的必备佳品 :-)

NULL 指针常量

说明

C++11 中定义了语言级的 NULL 指针常量:nullptr。

优点

  • 比起我们自定的“NULL”宏来说,nullptr 常量更利于函数重载。

缺点

  • 无。

总结

无论是 NULL 还是 nullptr,都应尽可能一致地使用(尽量避免混用)。

强类型枚举

说明

C++11 通过“enum class ...”的语法为枚举提供了强类型支持,同时还允许用户指定枚举的具体类型,如:16位无符号整形;32位整形;64位整形等等。

优点

  • 强类型检查杜绝了不同枚举类型之间的比较或枚举与整形之间的比较,能够在编译时发现更多错误。
  • 可指定枚举的位宽大大方便了很多原来不方便使用枚举的场合(例如:可以打破 32 位限制、可以显式强制保证 8 位尺寸等等)。

缺点

  • 无。

总结

推荐使用。

改进的大于号解析

说明

定义模板实例时,不再需要小心地在多个连续的大于号之间添加空格了。

优点

  • 不必再担心模板定义中的“>>”被解析为右移操作。

缺点

  • 无。

总结

早就该有的特性,这算是语言级的 bugfix?

显式类型转换操作

说明

在 C++11 中,explicit 修饰符已可应用于类型转换操作,帮助编译器更好地完成类型转换和函数重载判定。

优点

  • 一个与转换操作的类型严格匹配的场合可以自动应用显式类型转换,这使得显式类型转换操作可以在不降低易用性的前提下,提供更好的类型匹配。

缺点

  • 无。

总结

基本上也属于是早就该有的特性,相信很多程序员都试着写过“explicit operator bool()”、“explicit operator INT64()”之类的东东。

模板别名

说明

可以使用 using 语法来定义模板实例的别名。效果跟 typedef 基本一样。

优点

  • 无。

缺点

  • 官方的说法是 C++03 不允许通过 typedef 定义包含整形模板参数的实例。例如:“typedef CHandle< T, DestoryDeletor<T>, CAtomicINT32, NULL > HT;”语句按照 C++03 标准来说是非法的(假设“NULL”被定义为 0)。using 变种主要就是用来解决这个问题的。但实际上我已经在各种版本的 GCC、VC、Intel C 等编译器上这么用了很多年啊很多年。因此可以认为这个功能就等同于 typedef。

总结

意思不大,继续用 typedef 就好。如果觉得 using 更别致,那么就请一致地使用(尽量避免混用)。

强化的联合(union)类型

说明

在 C++11 中 union 中已可包含部分非 POD 类型,也可以给 union 定义构造函数和方法了。

优点

  • 放宽了 union 对部分非 POD 成员的限制无疑会使其更有用。
  • 允许为其定义构造和方法进一步方便了使用。

缺点

  • 无论如何,由于 union 的先天性质,还是很难让其支持析构方法吧?
  • 其先天性质决定了,一个复杂 union 结构的可读性和可维护性恐怕都不怎么样。

总结

好用的特性,但要注意适可而止,不要把设计搞的太复杂。

模板的可变参数

说明

为模板方法提供了类似普通函数的可变数量参数(...)支持。

优点

  • 可极大地方便一些特殊模板的构建,例如:用于实现智能指针模板类中的“operator->*()”(成员指针解引用)操作,以及一些类似于“printf”的模板方法。

缺点

  • 无。

总结

早该有,也是补漏型的特性。

新的字符类型和字符串字面值定义

说明

C++11 中新增了 char16_t 和 char32_t 两中类型,用以应对 wchar_t 位宽不确定的问题。又新增了 UTF-8(u8)、UTF-16(u)和 UTF-32(U)字符串字面值定义方法。同时还新增了一种无需对引号(")和反斜杠(\)等特殊字符进行换码的裸字符串字面值定义方法(R),该方法还可以与前面提到的 UTF 字面值前缀结合使用(u8R, uR, UR),非常全面和实用。

优点

  • 明确了 UTF-8、UTF-16 和 UTF-32 字符类型及其字面值定义方法。
  • 对于经常要写些 HTML/XML 或者正则表达式之类字面值的童鞋来说,裸字符串定义方式可以免除换码的烦恼。听上去没啥,但是等 coding 的时候……那是谁用谁知道呀。

缺点

  • 和 wchar_t 及相关旧代码的兼容是个问题。

总结

好用的东东。

自定义字面值后缀

说明

就像“1000ul”表示“值为 1000 的无符号长整数”字面值一样,C++11 允许用户通过新的“operator ""”操作自定义字面值后缀解释器,例如:
CMyClass operator "" _mysuf(const char* literal_string);CMyClass iVar = 1234_mysuf;

优点

  • 用户可以方便地直接通过字面值来生成自定义类型的对象。

缺点

  • 不当使用将导致代码的可读性和可维护性急剧下降。

总结

这涉及到 coding 时方便与阅读/维护时方便之间的权衡,应慎用。

线程本地存储(TLS)

说明

新标准中,“thread_local”存储类可用来指定一个 TLS。

优点

  • C++11 中规定任何可以使用“static”存储类的地方,都可以使用 thread_local。也就是说,可以用它来修饰带有构造、析构方法的对象,使得 TLS 用起来非常简便。

缺点

  • 从 thread_local 语义到实际操作系统 TLS Slot API 之间的封装和映射可能带来额外的运行时开销。

总结

好用的东东。比起直接使用操作系统的 TLS Slot 虽然可能增加少许隐式的开销,但通常对 TLS 存取也没有太高的效率要求(当然至少要比使用互斥量等锁算法要高效才行)。

显式默认和禁用方法

说明

C++11 中,用户可以通过“= default”后缀修饰符,为每个类显式指定默认构造函数。也可以通过“= delete”后缀修饰构造函数和赋值等操作(通常用来实现禁止复制的语义)。

优点

  • 能够通过明确的方式显式限定这些特殊方法有助于增强代码的可读性和可维护性。

缺点

  • 无。

总结

是传统的,通过将拷贝构造和赋值操作声明为 private 来禁止复制或类似做法的最佳替代品。

long long 类型

说明

C++11 中新增了确保至少 64 bit 的 long long 类型。

优点

  • 无。

缺点

  • 无。

总结

还有什么可说的呢?同样是一个早就该有,而且各大编译器早已支持的特性。

static_assert

说明

类似 assert,但专为模板而生:不像 assert 在运行时才检查,static_assert 在模板实例化时执行。

优点

  • 可在实例化点对模板参数进行校验。

缺点

  • 无。

总结

描述和检查模板契约的好方法。

允许 sizeof 操作直接作用于类的成员

说明

允许 sizeof 操作直接作用于 的成员,例如:“sizeof CMyClass::m_nValue”。

优点

  • 个别模板定义会变得方便一点。

缺点

  • 无。

总结

C++03 虽然不允许类似:“sizeof CMyClass::m_nValue”的写法,但也允许 sizeof 作用与 对象 的成员,例如:“CMyClass iTest; return sizeof(iTest.a)”之类。而且除了事先不知道类型的模板以外,还真没什么地方需要这么用 sizeof 的。
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章