以下摘要部分内容。具体内容详见:http://zh.wikipedia.org/wiki/C%2B%2B11#.E9.A1.AF.E5.BC.8F.E8.99.9B.E5.87.BD.E6.95.B8.E9.87.8D.E8.BC.89
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年为止的提案,忽略新的提案[1]。最终于2011年8月12日公布,并于2011年9月出版。
显式虚函数重载
在 C++ 里,在子类中容易意外的重载虚函数。举例来说:
struct Base { virtual void some_func(); }; struct Derived : Base { void some_func(); };
Derived::some_func
的真实意图为何? 程序员真的试图重载该虚函数,或这只是意外? 这也可能是 base
的维护者在其中加入了一个与Derived::some_func
同名且拥有相同签名的虚函数。
另一个可能的状况是,当基类中的虚函数的签名被改变,子类中拥有旧签名的函数就不再重载该虚函数。因此,如果程序员忘记修改所有子类,运行期将不会正确调用到该虚函数正确的实现。
C++11 将加入支持用来防止上述情形产生,并在编译期而非运行期捕获此类错误。为保持向后兼容,此功能将是选择性的。其语法如下:
struct Base { virtual void some_func(float); }; struct Derived : Base { virtual void some_func(int) override; // 錯誤格式: Derive::some_func 並沒有 override Base::some_func virtual void some_func(float) override; // OK:顯式改寫 };
编译器会检查基底类型是否存在一虚拟函数,与派生类中带有声明override
的虚拟函数,有相同的函数签名(signature);若不存在,则会回报错误。
C++11 也提供指示字final
,用来避免类型被继承,或是基底类型的函数被改写:
struct Base1 final { }; struct Derived1 : Base1 { }; // 錯誤格式: class Base1 以標明為 final struct Base2 { virtual void f() final; }; struct Derived2 : Base2 { void f(); // 錯誤格式: Base2::f 以標明為 final };
以上的示例中,virtual void f() final;
声明一新的虚拟函数,同时也表明禁止派生函数改写原虚拟函数。
override
与final
都不是语言关键字(keyword),只有在特定的位置才有特别含意,其他地方仍旧可以作为一般指示字(identifier)使用。
空指针
早在 1972 年,C语言诞生的初期,常数 0 带有常数及空指针的双重身分。 C 使用 preprocessor macroNULL
表示空指针, 让 NULL
及 0
分别代表空指针及常数 0。 NULL
可被定义为 ((void*)0)
或是 0
。
C++ 并不采用 C 的规则,不允许将 void*
隐式转换为其他类型的指针。 为了使代码 char* c = NULL;
能通过编译,NULL 只能定义为0
。 这样的决定使得函数重载无法区分代码的语义:
void foo(char *); void foo(int);
C++ 建议 NULL
应当定义为 0
,所以foo(NULL);
将会调用 foo(int)
, 这并不是程序员想要的行为,也违反了代码的直观性。0 的歧义在此处造成困扰。
C++11 引入了新的关键字来代表空指针常数:nullptr
,将空指针和整数 0 的概念拆开。 nullptr
的类型为nullptr_t
,能隐式转换为任何指针或是成员指针的类型,也能和它们进行相等或不等的比较。 而nullptr
不能隐式转换为整数,也不能和整数做比较。
为了向下兼容,0
仍可代表空指针常数。
char* pc = nullptr; // OK int * pi = nullptr; // OK int i = nullptr; // error foo(nullptr); // 呼叫 foo(char *)
多任务存储器模型
C++标准委员会计划统一对多线程编程的支持。
这将涉及两个部分:第一、设计一个可以使多个线程在一个进程中共存的内存模型;第二、为线程之间的交互提供支持。第二部分将由程序库提供支持,更多请看线程支持。
在多个线程可能会访问相同内存的情形下,由一个内存模型对它们进行调度是非常有必要的。遵守模型规则的程序是被保证正确运行的,但违反规则的程序会发生不可预料的行为,这些行为依赖于编译器的优化和存储器一致性的问题。
thread-local的存储期限
在多线程环境下,让各线程拥有各自的变量是很普遍的。这已经存在于函数的区域变量,但是对于全局和静态变量都还不行。
新的thread_local存储期限(在现行的static、dynamic和automatic之外)被作为下个标准而提出。线程区域的存储期限会借由存储指定字thread_local
来表明。
static对象(生命周期为整个程序的运行期间)的存储期限可以被thread-local给替代。就如同其他使用static存储期的变量,thread-local对象能够以构造函数初始化并以解构式摧毁。
使用或禁用对象的默认函数
在传统C++中,若用户没有提供, 则编译器会自动为对象生成默认构造函数(default constructor)、 复制构造函数(copy constructor),赋值运算符(copy assignment operatoroperator=) 以及解构式(destructor)。另外,C++也为所有的类定义了数个全局运算符(如operator delete及operator new)。当用户有需要时,也可以提供自定义的版本改写上述的函数。
问题在于原先的c++无法精确地控制这些默认函数的生成。 比方说,要让类型不能被拷贝,必须将复制构造函数与赋值运算符声明为private,并不去定义它们。 尝试使用这些未定义的函数会导致编译期或连结期的错误。 但这种手法并不是一个理想的解决方案。
此外,编译器产生的默认构造函数与用户定义的构造函数无法同时存在。 若用户定义了任何构造函数,编译器便不会生成默认构造函数; 但有时同时带有上述两者提供的构造函数也是很有用的。 目前并没有显式指定编译器产生默认构造函数的方法。
C++11 允许显式地表明采用或拒用编译器提供的自带函数。例如要求类型带有默认构造函数,可以用以下的语法:
struct SomeType { SomeType() = default; // 預設建構式的顯式聲明 SomeType(OtherType value); };
另一方面,也可以禁止编译器自动产生某些函数。如下面的例子,类型不可复制:
struct NonCopyable { NonCopyable & operator=(const NonCopyable&) = delete; NonCopyable(const NonCopyable&) = delete; NonCopyable() = default; };
禁止类型以operator new配置存储器:
struct NonNewable { void *operator new(std::size_t) = delete; };
此种对象只能生成于 stack 中或是当作其他类型的成员,它无法直接配置于 heap 之中,除非使用了与平台相关,不可移植的手法。 (使用 placement new 运算符虽然可以在用户自配置的存储器上调用对象构造函数,但在此例中其他形式的 new 运算符一并被上述的定义 屏蔽("name hiding"),所以也不可行。)
= delete
的声明(同时也是定义)也能适用于非自带函数, 禁止成员函数以特定的形参调用:
struct NoDouble { void f(int i); void f(double) = delete; };
若尝试以 double 的形参调用 f()
,将会引发编译期错误, 编译器不会自动将 double 形参转型为 int 再调用f()
。 若要彻底的禁止以非int的形参调用f()
,可以将= delete
与模板相结合:
struct OnlyInt { void f(int i); template<class T> void f(T) = delete; };
long long int
类别
在 32 位系统上,一个 long long int
是保有至少 64 个有效比特的整数类别。C99 将这个类别引入了标准 C 中,目前大多数的 C++ 编译器也支持这种类别。C++11 将把这种类别添加到标准 C++ 中
标准库组件上的升级
目前的标准库能受益于 C++11 新增的一些语言特性。举例来说,对于大部份的标准库容器而言,像是搬移内含大量元素的容器,或是容器之内对元素的搬移,基于右值引用 (Rvalue reference) 的move
构造函数都能优化前述动作。在适当的情况下,标准库组件将可利用 C++11 的语言特性进行升级。这些语言特性包含但不局限以下所列:
- 右值引用和其相关的
move
支持 - 支持 UTF-16 编码,和 UTF-32 字符集
- 变长实参模板 (与右值引用搭配可以达成完美转送 (perfect forwarding))
- 编译期常数表达式
Decltype
- 显式类别转换子
- 使用或禁用对象的默认函数
此外,自 C++ 标准化之后已经过许多年。现有许多代码利用到了标准库; 这同时揭露了部份的标准库可以做些改良。其中之一是标准库的存储器配置器 (allocator)。C++11将会加入一个基于作用域模型的存储器配置器来支持现有的模型。
线程支持
虽然 C++11 会在语言的定义上提供一个存储器模型以支持线程,但线程的使用主要将以 C++11 标准库的方式呈现。
C++11 标准库会提供类型 thread
(std::thread
)。若要运行一个线程,可以创建一个类型thread
的实体,其初始实参为一个函数对象,以及该函数对象所需要的实参。通过成员函数 std::thread::join()
对线程会合的支持,一个线程可以暂停直到其它线程运行完毕。若有底层平台支持,成员函数std::thread::native_handle()
将可提供对原生线程对象运行平台特定的操作。
对于线程间的同步,标准库将会提供适当的互斥锁 (像是 std::mutex
,std::recursive_mutex
等等) 和条件变量 (std::condition_variable
和std::condition_variable_any
)。前述同步机制将会以 RAII 锁 (std::lock_guard
和std::unique_lock
) 和锁相关算法的方式呈现,以方便程序员使用。
对于要求高性能,或是极底层的工作,有时或甚至是必须的,我们希望线程间的通信能避免互斥锁使用上的开销。以原子操作来访问存储器可以达成此目的。针对不同情况,我们可以通过显性的存储器屏障改变该访问存储器动作的可见性。
对于线程间异步的传输,C++11 标准库加入了 以及 std::packaged_task
用来包装一个会传回异步结果的函数调用。 因为缺少结合数个 future 的功能,和无法判定一组 promise 集合中的某一个 promise 是否完成,futures 此一提案因此而受到了批评。
更高级的线程支持,如线程池,已经决定留待在未来的 Technical Report 加入此类支持。更高级的线程支持不会是 C++11 的一部份,但设想是其最终实现将创建在目前已有的线程支持之上。
std::async
提供了一个简便方法以用来运行线程,并将线程绑定在 std::future
。用户可以选择一个工作是要多个线程上异步的运行,或是在一个线程上运行并等待其所需要的数据。默认的情况,实现可以根据底层硬件选择前面两个选项的其中之一。另外在较简单的使用情形下,实现也可以利用线程池提供支持。