C++11特性维基百科总结

Posted OshynSong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11特性维基百科总结相关的知识,希望对你有一定的参考价值。

C++11标准发布已有一段时间了, 维基百科上有对C++11新标准的变化和C++11新特性介绍的文章. 我是一名C++程序员,非常想了解一下C++11. 英文版的维基百科看起来非常费劲,而中文版维基百科不是知道是台湾还是香港人翻译的然后由工具转换成简体中文的,有些术语和语言习惯和大陆程序不一样! 我决定结合这两个版本按照我自己的习惯把这篇文章整理到我的博客中.分享给关注我和关注C++11的朋友们. 当然了, 本人水平有限,英语水平也很一般,就把这个过程当做学习C++11的过程吧.文章中肯定会有很多错误或描述不恰当的地方. 非常希望看到的朋友能给我指出来.

以下是关于C++11的英文版本和中文版本维基百科的链接:

http://en.wikipedia.org/wiki/C++11

http://zh.wikipedia.org/wiki/C++0x

   目录

   0.简介

C++11,之前被称作C++0x,即ISO/IEC 14882:2011,是目前的C++编程语言的正式标准。它取代第二版标准ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998发布于1998年,第二版于2003年发布,分别通称C++98以及C++03,两者差异很小)。新的标准包含了几个核心语言增加的新特性,而且扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数除外)。最新的消息被公布在 ISO C++ 委员会网站(英文)。
ISO/IEC JTC1/SC22/WG21 C++ 标准委员会计划在2010年8月之前完成对最终委员会草案的投票,以及于2011年3月召开的标准会议完成国际标准的最终草案。然而,WG21预期ISO将要花费六个月到一年的时间才能正式发布新的C++标准。为了能够如期完成,委员会决定致力于直至2006年为止的提案,忽略新的提案。最终,于2011年8月12日公布,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于现行标准的草案,差异仅有编辑上的修正。
像C++这样的编程语言,通过一种演化的过程来发展其定义。这个过程不可避免地将引发与现有代码的兼容问题。不过根据Bjarne Stroustrup(C++的创始人,标准委员会的一员)表示,新的标准将几乎100%兼容现有标准。

 

1.来自以往版本标准的变更(候选变更)

C++的修订范围包括核心语言以及标准程序库。在开发2011版标准的各个特性的过程中,标准委员会坚持以下指导思想:
    * 维持与C++98,可能的话还有C之间的兼容性稳定性
    * 尽可能通过通过标准程序库来引进新的特性, 而不是扩展核心语言;
    * 能够促进编程技术的变更优先;
    * 改进 C++ 以帮助系统和程序库的设计,而不是引进只对特定应用有用的新特性;
    * 增强类型安全,给现行不安全的技术提供更安全的替代方案;
    * 增强直接与硬件协同工作的性能和能力;
    * 为现实世界中的问题提供适当的解决方案;
    * 实行零负担原则(如果某些功能要求的额外支持,那么只有在该功能被用到时这些额外的支持才被用到);
    * 使C++易于教学

注重对初学者的关注,因为他们构成了计算机程序员的主体。也因为许多初学者不愿扩展他们的C++知识,他们仅限于掌握C++中自己所专精的部分. 

 

2.核心语言的扩充

C++标准委员会的一个职责是开发语言核心. 核心语言被大幅改进的领域包括: 多线程支持, 泛型编程支持, 统一的初始化和提高性能.
    这篇文章将核心语言的特性和变化大致分为: 提高运行期性能, 提高编译期性能, 增强可用性, 和新特性4大类. 某些特性可以被划分到多个分类中, 但只会在主要体现该特性的分类中讨论一次.

 

3.提高核心语言运行性能
    以下特性主要是为了提高性能提或计算速度等设计的.

3.1 右值引用和移动构造
    在C++03及之前的标准中,临时对象(称为右值"R-values",因为通常位于赋值运算符的右边)的值是不能改变的,和C语言一样, 且无法和 const T& 类型做出区分。尽管在某些情况下临时对象的确会被改变,甚至有时还被视为是一个有用的漏洞。C++11新增加了一个非常量引用(non-const reference)类型,称作右值引用(R-value reference),标记为T &&。右值引用所引用的临时对象可以在该临时对象被初始化之后做修改,这是为了允许 move 语义。
    C++03 性能上被长期诟病的问题之一,就是其耗时且不必要的深拷贝。深拷贝会隐式地发生在以传值的方式传递对象的时候。例如,std::vector内部封装了一个C风格的数组和其元素个数,如果创建或是从函数返回一个std::vector的临时对象,要将其保存起来只能通过生成新的std::vector对象并且把该临时对象所有的数据复制进去。该临时对象和其拥有的內存会被销毁。(为简单起见,这里忽略了编译器的返回值优化)
    在 C++11中,std::vector有一个"移动构造函数",对某个vector的右值引用可以单纯地从右值复制其内部C风格数组的指针到新的vector中,然后将右值中的指针置空。因为这个临时对象不会再被使用,没代码会再访问这个空指针, 而且因为这个临时对象的内部指针是NULL,所以当这个临时对象离开作用域时它的内存也不会被释放掉.所以,这个操作不仅没有代价高昂的深拷贝, 还是安全的,对用户不可见的!

这个操作不需要数组的复制,而且空的临时对象的析构也不会销毁内存。返回vector临时对象的函数只需要返回std::vector<T>&&。如果vector没有move 构造函数,那么就会调用常规拷贝构造函数。如果有,那么就会优先调用move构造函数,这能够避免大量的内存分配和内存拷贝操作。

右值引用不用对标准库之外的代码做任何改动就可以为已有代码带来性能上的提升. 返回值类型为std::vector<T>的函数返回了一个std::vector<T>类型的临时对象,为了使用移动构造不需要显示地将返回值类型改为std::vector<T>&&, 因为这样的临时对象会被自动当作右值引用. 但是在c++03中, std::vector<T>没有移动构造函数, 带有const std::vector<T>& 参数的拷贝构造会被调用, 这会导致大量内存分配和拷贝动作.
出于安全考虑, 需要施加一些限制! 一个已命名的变量即使声明为右值,也不会被视为右值.想要获取一个右值,应该使用模板函数std::move<T>(). 右值引用也可以在特定情况下被修改, 主要是为了与移动构造函数一起使用!
由于"右值引用"这个词的自然语义,以及对"左值引用"(常规引用)这个词的修正, 右值引用可以让开发者提供完美的函数转发! 与可变参数模板结合时, 这个能力让模板函数能够完美地将参数转发给带有这些参数的另一个函数.这对构造函数的参数转发最为有用,创建一个能够根据特定的参数自动调用适当的构造函数的工厂函数.

3.2 泛化的常数表达式

  C++一直以来都有常量表达式的概念.这种表达式就像3+4这种在编译期和运行时都能得到相同结果的表达式. 常量表达式给编译器提供了优化的机会, 编译器计算出他们的值并把结果硬编码到程序中. 并且C++规格文档中有很多地方要求使用常量表达式. 例如,定义一个数组需要常量表达式(来指定数组大小), 枚举值必须是常量表达式.
      然而,常量表达式中从来都不允许调用函数或创建对象. 所以,像下面这样的简单代码却是非法的:

int get_five() return 5; 
int some_value[get_five() + 7]; // 创建一个包含12个整数的数组. 这种形式在C++中是非法的.

  这在C++03中是非法的, 因为get_five() + 7不是常量表达式. C++03的编译器在编译期没办法知道get_five()是常量.因为从理论上讲, 这个函数可以影响(改变)一个全局变量或调用其它非运行时常量函数等.
     C++11引入了constexpr关键字, 允许用户去保证一个函数或对象的构造函数是一个编译期常量.上面的例子可以写成下面这样:

constexpr int get_five() return 5; 
int some_value[get_five() + 7]; // 创建一个包含12个整数的数组. 这种形式在C++11中是合法的.

  这样可以让编译器理解并验证get_five()是一个编译期常量!作用在函数上的constexpr关键字对函数的行为施加了一些限制. 首先, 这个函数的返回值类型不能是void; 其次, 在函数体中不能声明变量或新类型; 第三, 函数体内只能包含声明语句,空语句和单个return语句且,return语句中的表达式也必须是常量表达式.
  在c++11之前, 变量的值只有在变量被声明为const类型,有常量表达式初始化它, 并且是整型或枚举类型时才能用在常量表达式中. C++11去掉必须是整数或枚举的限制,如果定义变量时用了constexpr关键字:

constexpr double earth_gravitational_acceleration = 9.8;
constexpr double moon_gravitational_acceleration = earth_gravitational_acceleration / 6.0;

  这种数据变量是隐式的常量, 必须用常量表达式初始化.想要构造用户定义类型的常量值,构造函数也必须用constexpr声明.

    3.3 对POD定义的修正                                                                                     在C++03中, 类或结构体必须遵守几条规则才能认为是POD类型.符合这种定义的类型能够产生与C兼容的对象(内存)布局, 而且可以被静态初始化.C++03标准对与C兼容或可以静态初始化的类型有严格的限制,尽管不是因技术原因导致编译器不接受这样的编码.如果创建了一个C++03的POD类型,又想要增加一个非虚成员函数, 那么这个类型就不再是POD类型了, 不能静态初始化,并且与C不兼容了, 尽管没有改变内存布局.

     C++11将POD的概念拆分成了两个独立的概念:平凡的(trivial)和标准布局(standard-layout), 放宽了几条POD规则. 一个平凡的(trivial)类型可以静态初始化. 这意味着可以通过memcpy来拷贝数据,而不必通过拷贝构造函数. 平凡类型的变量的生命期是从分配存储空间开始的,而不是从构造完成开始.

    平凡的类和结构体定义如下:

      1 有一个平凡的默认构造函数. 这可以使用默认构造函数语法, 例如SomeConstructor() = default;

    2 有平凡的拷贝和移动构造函数, 可以使用默认语法.

    3 有平凡的拷贝和移动赋值运算符, 可以使用转变语法.

    4 有一个平凡的析构函数, 必须是非虚函数.

   只有当一个类没有虚函数,没虚基类时它的构造函数才是平凡的. 拷贝和移动操作还要求类的所有非静态数据成员都是平凡的.

   一个类型是标准布局的,就意味着它将以与C兼容的方式来排列和打包它的成员.标准布局的类和结构体定义如下:

    1 没有虚函数

    2 没有虚基类

    3 所有的非静态数据成员都有相同的访问控制 (public, private, protected)

    4 所有的非静态数据成员, 包括基类中的, 都要在继承体系中的同一个类中.

    5 以上规则也适用于类体系中的所有基类和所有非静态数据成员.

    6 没有和第一个定义的静态数据成员相同类型的基类

    如果一个类型是平凡的(trivial),是标准布局的, 并且所有的非静态数据成员和基类都是POD类型的, 那么这个类型就是POD类型.                                                                                                                                                                        

  4 核心语言建构期性能提高

   4.1 外部模板                                                                在C++03中,只要在编译单元内遇到被完整定义的模板,编译器都必须将其实例化(instantiate)。这会大大增加编译时间,特别是模板在许多编译单元内使用相同的参数实例化。没有办法告诉C++不要引发模板的实例化.C++11引入了外部模板声明, 就像外部数据声明一样. C++03用下面的语法迫使编译器实例化一个模板:

    template class std::vector<MyClass>;
    而C++11提供下面的语法告诉编译器在当前编译单元中不要实例化这个模板:

    extern template class std::vector<MyClass>;

  5 核心语言可用性的增强

  这些特性存在的主要目的是为了让C++更使用. 这些特性可以改进类型安全, 最小化代码重复, 尽可能减少错误代码等.                                                                                                                                      

  5.1 初始化列表

  C++03从C语言继承了初始化列表这一特性. 在一对大括号中列出参数的方式来给出一个结构体或者数组, 这些参数值按照各个成员在结构体中的定义顺序来排列.这些初始化列表是递归的,所以一个结构体数组或包含另一个结构体的结构体可以使用它们.

      struct Object 
      float first;
      int second;
      ;
  
      Object scalar = 0.43f, 10; //One Object, with first=0.43f and second=10
      Object anArray[] = 13.4f, 3, 43.28f, 29, 5.934f, 17; //An array of three Objects

   这对于静态初列表或者只想把结构体初始化为某个特定值而言是非常有用的. C++提供了构造函数来初始化对象, 但这没有初始化列表方便.C++03只允许符合POD定义的类型使用初始化列表,非POD的类型不能使用,就连相当有用的STL容器std::vector也不行.C++11扩展了初始化列表, 使用它可以用在所有类上,包括像vector这样的标准容器.
   C++11把初始化列表的概念绑到一个叫做std::initializer_list的模板上.这允许构造函数或其他函数将初始化列表做为参数.例如:

      class SequenceClass 
      public:
      SequenceClass(std::initializer_list list);
      ;

  这使得可以从一串整数来创建SequenceClass对象, 例如:
      SequenceClass some_var = 1, 4, 5, 6;
      这个构造函数是一种特殊的构造函数,叫做初始化列表构造函数(initializer-list-constructor).有这种构造函数的类在统一初始化中会被特殊对待(详见5.2)
类型std::initializer_list<>是个第一级的C++11标准程序库类型. 但是,它们只能由C++11通过语法来静态构造!这个列表一经构造便可复制,虽然这只是copy-by-reference.初始化列表是常数;一旦被创建,其成员均不能被改变,成员中的数据也不能够被改变.
  因为初始化列表是真实类型,除了构造函数之外还能够被用在其他地方。常规的函数能够使用初始化列表作为参数。例如:    

      void FunctionName(std::initializer_list list);
    FunctionName(1.0f, -3.45f, -0.4f);
    标准容器也能够以这种方式初始化:
   std::vector v =  "xyzzy", "plugh", "abracadabra" ;
   std::vector v( "xyzzy", "plugh", "abracadabra" );
  

以上是关于C++11特性维基百科总结的主要内容,如果未能解决你的问题,请参考以下文章

C++11新特性:6—— C++11对模板实例化中连续右尖括号>>的改进

c_cpp 以递归方式做河内塔问题的基准.n = 20来自维基百科的伪代码:https://zh.wikipedia.org/wiki/汉诺塔

OO第三单元总结

隐马尔可夫模型(HMM)总结

java基础总结——概述

维基百科的存在是一个奇迹