MelonTeam 移动终端前沿技术的探索者

深入理解C++11(一)

2017-07-31
lukewu
ios

导语 从最初的代号C++0x到最终的名称C++11,C++的第二个真正意义上的标准姗姗来迟。

C++11是一种新语言的开端。虽然设计C++11的目的是为了要取代C++98/03,相比于C++03标准,C++11则带来了数量可观的变化,包括了约140个新特性,以及对C++03标准中约600个缺陷的修正。因此,从这个角度看来C++11更像是从C++98/03中孕育出的一种新语言。

       从使用上,Scott Mayers为C++11创建了另外一种有效的分类方式,Mayers根据C++11的使用者是类的使用者,还是库的使用者,或者特性是广泛使用的,还是库的增强的来区分各个特性。具体的,可以把特性分为类作者需要的(class writer,简称为”类作者”)、库作者需要的(library writer,简称为”库作者”)、所有人需要的(everyone,简称为”所有人”)、部分人需要的(everyone else,简称为”部分人”)。

1 C++11相对于C++98/03有以下几点的增强:

<1>.通过内存模型、线程、原子操作等来支持本地并行编程(Native Concurrency)。

<2>.通过统一初始化表达式、auto、declytype、移动语义等来统一对泛型编程的支持。

<3>.通过constexpr、POD等更好的支持系统编程。

<4>.通过内联命名空间、继承构造函数和右值引用等,以更好地支持库的构建。

2 保持与C99兼容(类别:部分人)

C语言发展中的大多数改进都被引入了C++语言标准中,但还是存在着一些属于C99标准的”漏网之鱼”。所以C++11将对以下C99特性的支持也都纳入了新标准中:(1)C99中的预定义宏 (2)func__预定义标识符 (3)_Pragma操作符 (4)不定参数宏定义__VA_ARGS (5)宽窄字符串连接

2.1  预定义宏

     相较于C89标准,C99语言标准增加一些预定义宏。C++11同样增加了对这些宏的支持,如下表:

宏名称

功能描述

—|—

STDC_HOSTED

如果编译器的目标系统环境中包含完整的标准C库,那么这个宏就定义为1,否则宏的值为0

STDC

C编译器用这个宏的值表示编译器的实现是否和C标准一致。C++11中这个宏是否定义以及定成什么值由编译器决定

STDC_VERSION

   C编译器通常用这个宏来表示所支持的C标准的版本。C++11中这个宏是否定义以及定成什么值由编译器决定

STDC_ISO_10646

   这个宏定义为一个yyyymml格式的整数常量,例如199712L,用来表示C++编译环境符合某个版本的ISO/IEC 10646标准

2.2  __func__预定义标识符

     __func__预定于标识符的基本功能是返回所在函数的名字。

2.3  _Pragma操作符

在C/C++标准中,#pragma是一条预处理的指令。简单的说,#pragma是用来向编译器传达语言标准以外的一些信息。如果在代码的头文件中定义了#pragma once语句,那么该指令会指示编译器,该头文件应该只被编译一次。在C++11中,定义了与预处理指令#pragma功能相同的操作符_Pragma格式如下:

       _Pragma(字符串字面量)

       相对于预处理指令#pragma,_Pragma是一个操作符,因此可以用在一些宏中。

2.4 变长参数的宏定义以及__VA_ARGS__

在C99标准中,程序员可以使用变长参数的宏定义,指在宏定义中参数列表的最后一个参数为省略号,__VA_ARGS__可以在宏定义的实现部分替换省略号所代表的字符串。

      #define PR(…) printf(VA_ARGS)

2.5  宽窄字符串的连接

在之前的C++标准中,窄字符串(char)转换为宽字符串(wchar_t)是未定义的行为。C++11的标准规定,窄字符串和宽字符串进行连接时,支持C++11标准的编译器将窄字符串转换成宽字符串,然后与宽字符串进行连接。

3.1 扩展的整形(类别:部分人)

      C++11中一共只定义了5种标准的有符号整型:(1)signed char (2)short int (3)int (4)long int (5)long long int。同时规定,每一种有符号整型都有一种对应的无符号整数版本,且有符号整型与其对应的无符号整型具有相同的存储空间大小。如与signed int对应的无符号版本的整型是unsigned int。

3.2 静态断言(类别:库作者)

3.2.1 断言:运行时与预处理时

断言(assertion)是编程中常用的手段。一般情况下,断言就是将一个返回值总是需要为真的判别式放在语句中,用于排除在设计的逻辑上不应该产生的情况。在某种意义上,断言并不是正常程序所必需的。不过对于调试程序来说,通常断言能够帮助开发GG快速定位那些违反了某些前提条件的程序错误。在C++中,头文件中提供了assert宏,用于在运行时进行断言。

main函数中对ArrayAlloc的使用没有满足n>0的条件,在运行时,出现Assertion n > 0 failed。

3.2.2 静态断言与static_assert

断言assert宏只有在程序运行时才能起作用。而#error只在编译器预处理是才能起作用。在某些场合,希望能在编译时做一些断言。如下述例子:

本例中,使用了assert断言,assert断言的作用是为了保证a和b两种类型的长度一致,这样bit_copy才能够保证复制操作不会遇到越界等问题。因为assert断言是一个运行时断言,如果出现bit_copy不被调用等情况,我们将无法触发该断言。实际上,为了解决上述问题,正确产生断言的时机应该在模板实例化时,即编译时期的断言,也可以称作为“静态断言”。在实际应用中,我们可以利用“除0”会导致编译器报错这个特性来实现静态断言。

无论是哪种方式的静态断言,缺陷都是非常明显的:诊断信息不够充分,不熟悉该静态断言实现的开发GG可能一时无法将错误对应到断言错误上,从而难以准备定位错误的根源。

在C++11的新标准中,引入了static_assert断言来解决上述问题。static_assert接收两个参数,一个是断言表达式,这个表达式通常需要返回一个bool值,一个是警告信息,通常是一段字符串。使用static_assert替换上述bit_copy的声明。

     再次编译上述代码,会出现如下信息:

因为static_assert是编译时期的断言,其使用范围不像assert一样受到限制。在通常情况下,static_assert可以用于任何名字空间。

3.3 noexcept修饰符与noexcept操作符(类别:库作者)

        相比较断言排除逻辑上不可能存在的状态,异常用于逻辑上可能发生的错误。在异常处理的代码中,开发GG有可能看到过如下的异常声明表达形式:

        void excpt_func() throw(int ,double){……}

在excpt_func()函数声明之后,定义了一个动态异常声明throw(int,double),该声明指出了excpt_func()可能抛出的异常的类型。实际情况下,该特性很少被使用。C++11的新标准中,使用noexcept替换了上述特性。在C++11中,如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数终止程序的运行。一般情况下,noexcept修饰符有两种形式:(1)void excpt_func() noexcept; (2)void excpt_func() noexcept(常量表达式);第二种形式中的常量表达式的结果会被转换成一个bool类型的值。若该值为true,表示函数不会抛出异常,反之,则有可能抛出异常。

       noexcept作为一个操作符是,通常可以用于模板。

       template

       void fun() noexcept(noexcept(T())){}

这里,fun函数是否是一个noexcept的函数,将由T()表达式是否抛出异常所决定。这里的第二个noexcept就是一个noexcept操作符。

#


说一说

目录