使用C++的从1加到100的程序用循环结构构重写以下代码段?

processor)是独特的微处理器,是以数字信号来处理大量信息的器件。其工作原理是接收模拟信号,转换为0或1的数字信号。再对数字信号进行修改、删除、强化,并在其他系统芯片中把数字数据解译回模拟数据或实际环境格式。它不仅具有可编程性,而且其实时运行速度可达每秒数以千万条复杂指令,远远超过通用微处理器,是数字化电子世界中日益重要的电脑芯片。它的强大数据处理能力和高运行速度,是最值得称道的两大特色。

目前在DSP平台上编程多使用汇编语言与C语言,为了追求代码的高效,过去一般用汇编语言来编制。DSP汇编语言简洁高效,能够直接操作DSP的内部寄存器、存储空间、外设,但可读性、可修改性、可移植性较差;随着DSP应用范围不断延伸,应用的日趋复杂,汇编语言在可读性、可修改性、可移植性和可重用性的缺点日益突出,软件需求与软件生产力之间的矛盾日益严重。DSP产业在约40年的历程中经历了三个阶段:第一阶段,DSP意味着数字信号处理,并作为一个新的理论体系广为流行。随着这个时代的成熟,DSP进入了发展的第二阶段,在这个阶段,DSP代表数字信号处理器,这些DSP器件使我们生活的许多方面都发生了巨大的变化。接下来又催生了第三阶段,这是一个赋能(enablement)的时期,我们将看到DSP理论和DSP架构都被嵌入到SoC类产品中。” 第一阶段,DSP意味着数字信号处理 。 80年代开始了第二个阶段,DSP从概念走向了产品,TMS32010所的出色性能和特性备受业界关注。方进先生在一篇文章中提到,新兴的DSP业务同时也承担着巨大的风险,究竟向哪里拓展是生死攸关的问题。当设计师努力使DSP处理器每MIPS成本降到了适合于商用的低于10美元范围时,DSP在军事、工业和商业应用中不断获得成功。

但由于DSF结构的特殊性,使得该平台上的C语言编译器无法充分发挥DSP器件的性能优势。同样功能的C语言程序,效率往往只有直接书写的汇编程序的几分之一甚至几十分之一。

TMS320C6000是TMS320系列产品中的新一代高性能DSP芯片,共分为两大系列。其中定点系列为TMS320C62xx和TMS320C64xx;浮点系列为TMS320C67xx。由于TMS320C6000的开发主要面向数据密集型算法,它有着丰富的内部资源和强大的运算能力。

C6000系列CPU中的8个功能单元可以并行操作,并且其中两个功能单元为硬件乘法运算单元,大大地提高了乘法速度。DSP采用具有独立程序总线和数据总线的哈佛总线结构,仅片内程序总线宽度就可达到256位;片内两套数据总线的宽度分别为32位;此外,DSP还有一套32位DMA专用总线用于传输。灵活的总线结构使得数据瓶颈对系统性能的限制大大缓解。C6000的通用寄存器组能支持32位和40位定点数据操作,另外C67xx和C64xx还分别支持64位双精度数据和64位双字定点数据操作。微处理器是低成本的,主要执行智能定向控制任务的通用处理器能很好执行智能控制任务,但是数字信号处理功能很差。而DSP的功能正好与之相反。在许多应用中均需要同时具有智能控制和数字信号处理两种功能,如数字蜂窝电话就需要监测和声音处理功能。因此,把DSP和微处理器结合起来,用单一芯片的处理器这两种功能,将加速个人通信机、智能电话、无线网络产品的开发,同时简化设计,减小PCB体积,降低功耗和整个系统的成本。

图1为C6000的软件开发流程图。图中阴影部分是开发C代码的常规流程,其他部分用于辅助和加速开发讨程。

C/C++源文件首先经过C/C++编译器(C/C++cornpiler)转换为C6000汇编源代码。编译器、器(optimizer)和交叠工具是C/C++编译器的组成部分。编译器使用户能一步完成编译、汇编和连接;器调整合修改代码以提高C程序的效率;交叠工具把C/C++语句和对应的汇编语句交叠列出。

汇编源代码再经过汇编器(Assembier)翻译为机器语言目标文件。机器语言是基于通用目标文件格式(CommON Object File Format,COFF)的。

连接器(Linker)连接目标文件,生成一个可执行文件。它要完成地址的重分配(Relocation)和解析外部引用(Resolve External References)。

得到可执行文件之后就可以进行调试。可用软件仿真器(Simulator)在PC机上对指令和运行时间进行精确仿真;用XDS硬件仿真器(Emulator)在目标板上进行调试。

调试通过后即可下载到目标板进行独立运行。

由于DSP应用的复杂度,在用C语言进行DSP软件开发时,一般先在基于通用微处理器的PC机或工作站上对算法进行仿真,仿真通过后再将C程序移植到DSP平台中。

所以,DSP的软件开发与优化流程主要分为3个阶段:C代码开发阶段;C代码优化阶段;手工汇编代码重编写阶段。如图2所示。

在图2中,第一阶段:没有C6000知识的用户能开发自己的C代码,然后使用CCS中的代码剖析工具,确定C代码中可能存在的低效率段,为进一步代码优化做好准备。第二阶段:C代码优化阶段。在这个阶段,主要利用intrinsics函数以及编译器编译选项来提高代码的性能。优化后利用软件模拟器检查代码的效率,如仍不能达到期望的效率,则进入第三阶段。第三阶段:写线性汇编优化。在这个阶段中,用户把最耗费时间的代码抽取出来,重新用线性汇编写,然后使用汇编优化器优化这些代码。在第一次写线性汇编时,可以不考虑流水线和寄存器分配。然后,提高线性汇编代码性能,往代码中添加更多的细节。

为了使C/C++代码获得最好的性能,可以使用编译选项、软件流水、内联函数和循环展开等方法来对代码进行优化,以提高代码执行速度,并减小代码尺寸。

3.2.1 编译器选项优化

C/C++编译器可以对代码进行不同级别的优化。高级优化由专门的优化器完成,低级的和目标DSP有关的优化由代码生成器完成。图3为编译器、优化器和代码生成器的执行图。

当优化器被激活时,将完成图3所示的过程。C/C++语言源代码首先通过一个完成预处理的解析器(Parser),生成一个中间文件(。if)作为优化器(Optimi-zer)的输入。

最简单执行优化的方法是采用cl6x编译程序,在命令行设置一On选项即可。n是优化的级别(n为0,1,2,3),它控制优化的类型和程度。

软件流水是编排循环指令,使循环的多次迭代并行执行的。使用一02和一03选项编译C/C++程序时,编译器就从程序中收集信息,尝试对程序循环做软件流水。

图4显示一个软件流水循环。图4中A,B,C,D和E表示1次迭代中的各条指令;A1,A2,A3,A4和A5表示一条指令执行的各阶段。循环中,一个周期最多可并行执行5条指令,即图中阴影部分所示的循环核(Loop Kernel)部分。

通过下面的方法改进C语言程序,可使编译出的代码性能显着提高:

(1)使用intrinsics(内联函数)替代复杂的C/C++代码;

(2)使用字(Word)访问存放在32位寄存器的高16位和低16位字段的数据;

(3)使用双字访问存放在64位寄存器的32位数据(仅指C64xx/C67XX)。

C6000编译器提供了许多内联函数,它们直接对应着C62X/C64X/C67X指令可快速优化C代码。这些内联函数不易用C/C++语言其功能。内联函数用前下划线“_”特别标示,其使用方法与调用函数一样。例如C语言的饱和加法只能写为需要多周期的函数:

这段复杂的代码可以用_sadd()内联函数实现,它是一个单周期的C6x指令。

要提高C6000数据处理率,应使一条Load/STore指令能访问多个数据。C6000有与内联函数相关的指令,例如_add2(),_mpyhl(),_mpylh()等,这些操作数以16位数据形式存储在32位寄存器的高位部分和低位部分。当程序需要对一连串短型数据进行操作时,可使用字1次访问2个短型数据,然后使用C6000相应指令来处理数据。相似的在C64x或C67x中,有时需要执行64位的LDDW来访问两个32位数据,4个16位数据,甚至8个8位数据。

循环展开是改进性能的另,即把小循环的迭代展开,以让循环的每次迭代出现在代码中。这种方法可增加并行执行的指令数。

有3种使循环展开的方法:

(1)编译器自动执行循环展开;

(2)在程序中使用UNROLL伪指令建议编译器做循环展开;

(3)用户自己在C/C++代码中展开。

在对C/C++代码使用了所有的C/C++优化手段之后,如果仍然不满意代码的性能,就可以写线性汇编程序,然后用汇编优化器进行优化,生成高性能的代码。

使用C6000的剖析工具(Profiling Tools)可以找到代码中最耗费时间的部分,就是这部分需要用线性汇编重写。线性汇编代码与汇编源代码相似,但是,线性汇编代码中没有指令延迟和寄存器使用信息。

写线性汇编代码时,需要知道:汇编优化器伪指令、影响汇编优化器行为的选项、TMS320C6000指令、线性汇编源语句语法、指定寄存器或寄存器组、指定功能单元、源代码注释等。

3.3.2 汇编优化器优化

汇编优化器的任务主要有:

(1)编排指令,最大限度的利用C6000的并行能力;

(3)为源代码分配寄存器。

C/c++代码优化比传统的代码优化要方便的多,但要真正发挥其芯片的工作效率还是需要一定的经验和技巧。这不仅要求开发人员熟悉其硬件体系,还要求对编译器的编译原理有一定的理解。另外,在C语言层面上要达到DSP芯片的峰值即8条指令并行是很难的,大多情况下都只能达到6.7条指令并行。在实际开发中,若优化结果已达到6,7条指令并行却还离实时的要求相差很远,再花大量的人力去力求达到8条指令并行是不经济的。


C++11(草案原名C++0x)最终国际投票已于2011年8月10日结束,所有国家都投出了赞成票。国际标準化组织(ISO)和国际电工委员会(IEC)于 2011年9月1日出版发布C++11标準,正式名称为:

新标準的pdf文档可以在ISO官方网站购买获取。

C++委员会的主要焦点是在语言核心的发展上。因此C++0x的发表日期取决于这部份标準的作业进度。

核心语言的领域将被大幅改善,包括多执行绪支持、泛型编程、统一的初始化,以及表现的加强。

在此分成4个区块来讨论核心语言的特色以及变更: 运行期表现强化、建构期表现强化、可用性强化,还有崭新的机能。某些特色可能会同时属于多个区块,但在此仅于其最具代表性的区块描述该特色。

2011年3月27日,IS0 C++ 委员会正式批准了C++程式语言国际标準最终草案(FDIS)。标準本身已经完成,接下来将是根据委员会会议修改意见更新工作草案,预计将用三周时间完成FDIS草案,然后交给日内瓦的ITTF,最新的C++标準将在夏天发布,先前被临时命名为C++0x的新标準将被称为C++ 2011。从2003年发布的C++03到2011年的C++

在旧标準C++语言中,临时量(术语为右值,因其出现在赋值表达式的右边)可以做参数传给函式,但只能被接受为const &类型。这样函式便无法区分传给const &的是真正的右值还是普通const变数。而且,由于类型为const &,函式也无法改变所传对象的值。

C++0x将增加一种名为右值引用的新的引用类型,记作typename &&。这种类型可以被接受为非const值,从而允许改变其值。这种改变将允许某些对象创建转移语义。

比如,一个std::vector,就其内部实现而言,是一个C式数组的封装。如果需要创建vector临时量或者从函式中返回vector,那就只能通过创建一个新的vector并拷贝所有存于右值中的数据来存储数据。之后这个临时的vector则会被销毁,同时删除其包含的数据。

有了右值引用,一个参数为指向某个vector的右值引用的std::vector的转移构造器就能够简单地将该右值中C式数组的指针複製到新的vector,然后将该右值清空。这里没有数组拷贝,并且销毁被清空的右值也不会销毁保存数据的记忆体。返回vector的函式现在只需要返回一个std::vector<>&&。如果vector没有转移构造器,那幺结果会像以前一样:用std::vector<> &参数调用它的拷贝构造器。如果vector确实具有转移构造器,那幺转移构造器就会被调用,从而避免大量的记忆体分配。

考虑到安全因素,具名变数即使被声明为右值类型也不会被当作右值。如需把它当作右值,须使用库函式std::move()。

出于右值引用定义的本质特徵以及某些对左值引用(常规引用)定义的修改,右值引用允许程式设计师提供函式参数的完美转发。当与模板变参相结合时,这种能力可以允许函式模板完美地将参数转发给接受那些参数的其他函式。这在转发构造器参数时尤为有用:可以创建一些能自动调用具有相应参数构造器的工厂函式。

C++语言一直具有常量表达式的概念。这些诸如3+4之类的表达式总是产生相同的结果且不具备副作用。常量表达式给编译器带来了最佳化的可能,而编译器也经常在编译期执行此类表达式并将结果存放在程式中。此外,C++语言规范中有一些地方需要使用常量表达式。定义数组需要常量表达式,而枚举值也必须是常量表达式。

然而,每当碰到函式调用或对象构造,常量表达式便不再有效。所以简单如下例便不合法:

这段代码在C++中不合法,因为GetFive() + 5不是一个常量表达式。编译器无从知晓GetFive在运行期是否产生常量。理论上,这个函式可能会影响某个全局变数,或者调用其他运行期产生非常量的函式。

C++0x将引入constexpr关键字,此关键字将使用户能保证某个函式或构造器在编译期产生常量。上例可被改写如下:

这段代码将使编译器理解并确认GetFive是个编译期常量。

在函式上使用constexpr将对函式功能施加严格的限制。首先,函式必须返回非void类型。其次,函式体必须具有"return /expr/"的形式。第三,expr在参数替换后必须是常量表达式。该常量表达式只能调用其他定义为constexpr的函式,只能使用其他常量表达式数据变数。第四,常量表达式中一切形式的递归均被禁止。最后,这种带constexpr的函式在编译单元中必须先定义后调用。

变数也可被定义为常量表达式值。

常量表达式数据变数隐含为常量。它们只能存放常量表达式或常量表达式构造器的结果。

为了从用户自定义类型中构建常量表达式数据值,构造器在声明时可带constexpr。同常量表达式函式一样,在编译单元中常量表达式构造器也必须先定义后使用。常量表达式构造器函式体必须为空,而且它必须用常量表达式构造其成员。这种类型的析构器必须是平凡的。

由常量表达式拷贝构造的类型也必须被定义为constexpr,以使它们能从常量表达式函式中作为值被返回。类的任何成员函式,包括拷贝构造器和操作符重载,都能被声明为constexpr,只要它们符合常量表达式函式的定义。这就允许编译器在编译期不仅能拷贝类对象,也能对其实施其他操作。

常量表达式函式或构造器可以用非constexpr参数来调用。就如同一个constexpr整数常量可以被赋给一个非constexpr变数一样,constexpr函式也可用非constexpr参数来调用,并且其结果也可存放在非constexpr变数中。此关键字只是提供了在一个表达式的全部成员均为constexpr时其结果为编译期常量的可能性。

在标準C++语言中,要让结构成为POD类型必须满足某几条规则。有充分理由让一大堆类型满足这些规则(定义);只要满足这些规则,结构的实现将产生兼容于C的对象布局。然而,在C++03中这些规则过于严格。注:POD,Plain Old Data,指POD用来表明C++中与C相兼容的数据类型,可以按照C的方式来处理(运算、拷贝等)。非POD数据类型与C不兼容,只能按照C++特有的方式进行使用。

C++0x将放鬆某些关于POD的限制规则。

如果一个类或结构是平凡的,具有标準布局的,且不包含任何非POD的非静态成员,那幺它就被认定是POD。平凡的类或结构定义如下:

2.具有一个平凡的拷贝构造器。(可以使用预设构造器语法)

3.具有一个平凡的拷贝赋值运算符。(可以使用预设语法)

4.具有一个非虚且平凡的析构器。

一个具有标準布局的类或结构被定义如下:

1.所有非静态数据成员均为标準布局类型。

5.所有基类均为标準布局类型。

6.没有任何基类的类型与类中第一个非静态成员相同。

7.要幺全部基类都没有非静态数据成员,要幺最下层的子类没有非静态数据成员且最多只有一个基类有非静态数据成员。总之继承树中最多只能有一个类有非静态数据成员。所有非静态数据成员必须都是标準布局类型。

在标準C++语言中,如果在某一个编译单元中编译器碰到一个参数完全指定的模板,它就必须实例化该模板。这种做法可能大大延长编译时间,尤其在许多编译单元使用同样的参数实例化该模板时。

C++0x将引入外部模板的概念。C++已经拥有了迫使编译器在某一地点实例化模板的语法:

C++所缺乏的是防止编译器具现化某个模板的能力。C++0x只是简单地将语法扩展为:

这段代码将告诉编译器不要在这个编译单元实例化此模板。

标準C++语言从C语言中借入了初始化列表概念。根据这一概念,结构或数组可以通过给定一串按照结构中成员定义的次序排列的参数来创建。初始化列表可以递归创建,因此结构数组或包含其他结构的结构也能使用初始化列表。这对于静态列表或用某些特定值初始化结构而言非常有用。C++语言中存在能让对象初始化的构造器特性。但构造器特性本身并不能取代初始化列表的所有功能。标準C++允许类和结构使用初始化列表,但它们必须满足POD的定义。非POD的类不能使用初始化列表,一些C++式的容器如std::vector和boost::array也不行。

C++0x将把初始化列表绑定为一种名为std::initializer_list的类型。这将允许构造器及其他函式接受初始化列表作为其参数。比如:

这段代码将允许SequenceClass用一串整数构造,如下所示:

这种构造器是一种特殊类型的构造器,名为初始化列表构造器。具有这种构造器的类在统一的初始化形式中将被特殊对待。

std::initializer_list<>类在C++0x标準库中将成为一等公民。但是这个类的对象只能通过使用{}语法由C++0x编译器静态构建并初始化。列表一旦构建即可被拷贝,儘管只是引用拷贝。初始化列表是常量,一旦构建,组成列表的成员以及其成员所包含的数据便无法改变。

由于初始化列表是一种真实的类型,因此在类构造器之外的地方也能使用。常规函式也可接受初始化列表作为其参数。比如:

另外,标準容器也可用初始化列表初始化。比如:

标準C++在类型初始化中存在一些问题。语言中存在几种类型初始化方式,但替换使用的话产生的结果不尽相同。传统的构造语法看起来更像函式声明。必须採取措施以使编译器不把对象构造误认为函式声明。只有集合类型和POD类型能用集合初始化器初始化(用SomeType var = {/*stuff*/};).

C++0x将提供一种能作用于任何对象的完全统一的类型初始化形式。这种形式对初始化列表语法作了扩展:

var1的初始化的运作方式就如同一个C式的初始化列表。每个public变数都将用初始化列表中的值初始化。如果需要,隐式类型转化将被使用,并且如果没有隐式类型转化可供使用,编译器将报告编译失败。

var2的初始化只是简单地调用构造器。

统一的初始化对象构造将消除在某些情况下指定类型的需要:

这种语法会自动使用const char *调用std::string进行初始化。程式设计师也可以使用下面的代码:

统一的初始化形式不会取代构造器语法。某些情况下仍然需要构造器语法。如果一个类具有初始化列表构造器(TypeName(initializer_list);),,那幺只要初始化列表符合该构造器的类型,初始化列表构造将优先于其他构造形式。

C++0x版本的std::vector将拥有匹配与模板参数的初始化列表构造器。这就意味着下面这段代码:

这段代码将调用初始化列表构造器,而不会调用std::vector中接受单个长度参数并创建相应长度的vector的构造器。为了调用后一个构造器,用户需要直接使用标準构造器语法。

在标準的C++和C语言中,变数在使用时必须明确指定其类型。然而,随着模板类型及模板元编程的到来,表述某些定义完好的函式的返回值的类型变得不那幺容易了。由此,在函式中存储中间值也变得困难,用户有可能需要了解某个模板元编程库的内部结构才行。

C++0x将通过两种方式来缓解这些困难。首先,带有明确初始化的变数定义将可以使用auto关键字。这种初始化将创建与初始化器类型相同的变数。

someStrangeCallableType的类型将等同于任何由boost::bind所返回的适合于这些特定参数的模板函式的类型。编译器很容易知道其类型,用户则不然。

otherVariable的类型也定义完好,但用户更容易推定其类型。该变数是整型,也就是整型常量的类型。

另外,关键字decltype可用于在编译期确定某个表达式的类型。比如:

这种用法相对于auto可能更有效,因为auto变数的类型只有编译器才知道。而且,对于那些大量使用操作符重载及特化类型的代码,使用decltype来推导表达式的类型也很有用。

auto在减少代码冗余性方面也很有用。比如,写下面这段代码时:

程式设计师可以使用下面这种更短的形式:

当程式设计师在开始使用嵌套容器时,这两段代码的区别将更加明显,儘管在这种情况下使用typedef也是一种减少代码的好方法。

C++库Boost定义了几个区间概念。区间代表了与容器相类似的列表中两点之间的可控列表。已序容器是区间的超集。已序容器中的两个叠代器也能定义一个区间。这些概念和算法都将被融入C++0x的标準库中。然而,C++0x还将提供一种专用的语言设施来运用区间概念。

for语句将使区间概念上的循环更容易:

新的for循环的第一部分定义了用于在区间上循环的变数。和普通for循环中声明的变数一样,该变数的作用域也仅限于循环之内。置于":"之后的第二部分则表示将进行循环的区间。在这种情况下,存在一个约束映射可以将C式数组转化为区间。

进行循环的区间还可以是std::vector,或任何符合区间概念的对象。

在标準C++语言中,尤其在使用诸如sort和find之类的标準库算法函式时,用户总是希望在算法函式调用的触发点附近定义谓词函式。在这一方面语言中只有一种机制可供利用:在函式中定义类。通常这种做法既啰嗦又笨重。另外,标準C++语言不允许在函式中定义的类充当模板参数,所以这种做法行不通。

显而易见,解决方案在于允许定义lambda表达式和 lambda函式。C++0x将允许定义lambda函式。

lambda函式可以定义如下:

此无名函式的返回值类型为decltype(x+y)。只有lambda函式的形式为"return /expression/"时,返回值类型才能省略。因此这种lambda函式内部只能有一句语句。

返回值类型也可像下例那样明确指定。一个更为複杂的例子:

在此例中,临时变数z被创建并用于存储中间值。和普通函式一样,中间值在多次函式调用之间不会被保存。

如果lambda函式不返回值,即返回值类型为void的话,该返回值类型也可完全省略。

在lambda函式作用域範围内被定义的变数的引用也能被使用。这些变数的合集通常被称为闭包。闭包可以定义并使用如下:

这段代码将显示列表中所有元素的总和。变数total将被存为该lambda函式相应的闭包的一部分。由于闭包变数total是栈变数total的引用,使用前者可以改变后者的值。

为栈变数生成的闭包变数也可以不用引用操作符/&/定义,这种情况下lambda函式将拷贝其值。这种做法将促使用户明确声明其意图:是引用栈变数还是拷贝栈变数。引用栈变数可能会产生危险。如果某个lambda函式将在所创建的作用域之外被引用(比如将此lambda函式存放在std::function(C++0x标準)对象中可以做到这一点),那幺用户必须保证该lambda函式没有引用任何栈变数。

对于那些可以保证只在其所创建的作用域内被执行的lambda函式,使用栈变数无须通过显式引用:

这种lambda函式的具体内部实现可能会有所不同,但可以预期这种lambda函式可能不会保存所有栈变数的引用而是会保存函式创建时的栈指针。

如果不用[&]而用[=],那幺所有被引用的变数都将被拷贝,从而允许lambda函式在原有变数生命期结束后仍然能够被使用。

预设指示符还能和参数列表结合使用。比如,如果用户希望只拷贝其中一个变数的值,而对其他变数使用引用,则可使用下面的代码:

这段代码将导致total被存为引用,而value则会被存为拷贝。

如果一个lambda函式由一个类的某个成员函式定义,那幺此lambda函式便被认定为该类的友元。这种lambda函式可以使用属于该类类型的对象的引用并访问其内部成员。

只有当lambda函式在SomeType的某个成员函式中创建时这段代码才能工作。

对于指向当前成员函式所隶属对象的this指针,其处理有些特殊:必须在lambda函式中明确指定。

Lambda函式是一些类型取决于编译器的函式对象。它们的类型只对编译器开放。如果用户希望把lambda函式当作参数,那幺要幺参数相应类型为模板,要幺创建一个std::function用于保存lambda函式。使用auto关键字则可以将lambda函式保存在局部变数中。

然而,如果lambda函式的所有闭包变数均为引用,或者lambda函式根本没有闭包变数,那幺所产生的函式对象将具有一种特殊类型:std::reference_closure。其中R(P)是带返回值的函式签名。这样做的理由在于期望此种类型的效率能好于使用std::function。

标準C语言的函式声明语法对于C语言的特性集来说是完美无缺的。由于C++语言演化自C语言,C++语言保留了相关的基本语法并在需要时进行扩充。然而,当C++变得更为複杂时,这种语法也暴露了一些局限性,尤其是在模板函式声明中。比如,以下代码在C++03中不合法:

类型Ret为任何LHS和RHS相加所产生的类型。即使有了前面所讲述的C++0x的decltype功能,仍然不行:

这一段并非合法的C++代码,因为lhs和rhs尚未定义,只有在词法分析器分析出函式原型的其余部分之后这两者才能成为有效的标识符。

为解决这一问题,C++0x将引入一种新型的函式定义和声明的语法:

这一语法也能用于更为平常的函式声明和定义中:

在C++语言中,模板类和模板函式必须对它们所接受的类型施加某些限制。比如,STL容器要求容器中的类型必须可以赋值。与类继承所展示的动多态(任何能接受Foo&类型对象作为参数的函式也能传入Foo的子类型)有所不同,任何类只要支持某个模板所使用的操作,它就能被用于该模板。在函式传参数的情况下,参数所必须满足的需求是清晰的(必须是Foo的子类型),而模板的场合下,对象所需满足的接口则是隐含在模板实现当中的。约束则提供了一种将模板参数所必需满足的接口代码化的机制。

引入约束的最初动因在于改进编译错误信息的质量。如果程式设计师试图使用一种不能提供某个模板所需接口的类型,那幺编译器将产生错误信息。然而,这些错误信息通常难以理解,尤其对于新手而言。首先,错误信息中的模板参数通常被完整拼写出来,这将导致异常庞大的错误信息。在某些编译器上,简单的错误会产生好几K的错误信息。其次,这些错误信息通常不会指向错误的实际发生地点。比如,如果程式设计师试图创建一个其成员为不具备拷贝构造器对象的vector,首先出现的错误信息几乎总是指向vector类中试图拷贝构造其成员的那段代码。程式设计师必须具备足够的经验和能力才能判断出实际的错误在于相应类型无法完全满足vector所需要的接口。

在试图解决此问题的过程中,C++0x为语言添加了约束这一特性。与OOP使用基类来限制类型的功能相似,约束是一种限制类型接口的具名结构。而与OOP所不同的是,约束定义并非总是与传入模板的参数类型明确相关,但它总是与模板定义相关:

这里没有用/class /或/ typename/将模板参数指定为任意类型,而是使用了/LessThanComparable/这个之前定义的约束。如果某个传入/min/模板参数的类型不符合/LessThanComparable/约束的定义,那幺编译器将报告编译错误,告诉用户用来具现化该模板的类型不符合/LessThanComparable/约束。

下面是一个更一般化的约束形式:

关键字/requires/之后为一串约束的声明。它可以被用于表述涉及多个类型的约束。此外,如果用户希望当类型匹配该约束时不要使用某个特定模板,也可以用/requires !LessThanComparable/。可以像模板特化那样使用这种机制。一个通用模板可能通过显式禁用一些特性丰富的约束来处理具有较少特性的类型。而这些约束则可通过特化利用某些特性来取得更高的效率并实现更多的功能。

这个例子中的关键字/auto/意味着任何类型只要支持约束中所指定的操作便被认定支持该约束。如果不使用/auto/关键字,为声明某个类型支持该约束就必须对该类型使用约束映射。

该约束声明任何类型只要定义了接受两个参数并返回bool型的<操作符就被认为是/LessThanComparable/。该操作符不一定是一个自由函式,它也可以是T类型的成员函式。

约束也可以涉及多个类型。比如,约束能表示一个类型可以转换为另一个类型:

为了在模板中使用这个约束,模板必须使用一种更为一般化的形式:

约束可以组合运用。比如,给定一个名为/Regular/的约束

与继承相似,约束也可派生自另一约束。与类继承相似,满足派生约束所有限制条件的类型必须满足基本约束的所有限制条件。约束派生定义形同类派生:

类型名可以与约束相关。这将施加一些限制条件:在使用这些约束的模板中,这些类型名可供使用:

约束映射允许某些类型被显式绑定到某个约束。如有可能,约束映射也允许在不改变类型定义的前提下让该类型採用某个约束的语法。比如下例:

这个约束映射填补了当/InputIterator/映射作用于/char*/类型时所需要的类型名。

为增加灵活性,约束映射本身也能被模板化。上例可以被延伸至所有指针类型:

此外,约束映射也可充当迷你类型,此时它会包含函式定义以及其他与类相关的结

这个约束映射将允许任何接受实现了/Stack/约束的类型的模板也接受

做法将允许一个已经存在的对象在不改变其定义的前提下,转换其接口并为模板函

最后需要指出的是,某些限制条件可以通过静态断言来检测。这种手段可以用来检

测那些模板需要但却面向其他方面问题的限制条件。

在标準C++语言中,构造器不能调用其他构造器。每个构造器要幺独自构建类的所有成员要幺调用某个公共成员函式。基类的构造器不能直接暴露给派生类:即便基类的构造器更合适,子类也必须实现自己的构造器。类的非静态数据成员不能在其声明的场所初始化,它们只能在构造器中初始化。

C++0x将为所有这些问题提供解决方案。

C++0x将允许构造器调用其他伙伴构造器(被称为委託)。如此,只需添加少量代码,构造器便能利用其他构造器的行为。另外一些语言,比如Java和C#,允许这样做。语法如下:

这就产生了一个问题:C++03认为一个对象在其构造器执行完毕时才能构建完成,而C++0x则认为一个对象在任何构造器执行完毕时都将构建完成。由于多个构造器被允许执行,这将导致每个委託构造器都可能在一个已经构造完成的对象上执行操作。派生类构造器将在基类的所有委託构造器执行完毕后执行。

关于基类构造器,C++0x将允许一个类指定需要继承的基类构造器。这意味着C++0x编译器将产生代码用于类继承,即将子类构造转发为基类构造。注意这是一个要幺全部要幺没有的特性:或者全部构造器被转发,或者没有构造器被转发。另外注意在多重继承的情况下有些限制,比如类构造器不能继承来自两个类的具有相同签名的构造器。同时子类构造器的签名也不能与用来继承的基类构造器相匹配。

关于成员初始化,C++0x将允许以下语法:

该类的任何构造器都将把iValue初始化为5,除非它提供自己的实现来改变这种行

为。所以上面的空构造器将按照类的定义来初始化iValue,而接受int的构造器则

会用给定的参数来初始化iValue。

在现行标準中,常量0既是常量整数又是空指针,充当着双重角色。这一行为自1972年C语言早期以来便一直存在。

多年来,程式设计师为了避免这种语义模糊多採用标识符NULL来代替0。然而,两项C++语言的设计选择集中产生了另一项语义模糊。在C语言中,NULL作为预编译宏被定义为((void*)0)或0。在C++语言中,void*不能隐式转换为其他指针类型,因而在前项定义下,简单如char* c = NULL的代码会通不过编译。为解决此问题,C++确保NULL展开为0,并作为一种特例允许其转换为其他指针类型。这一选择在同重载机制互动时产生了麻烦。比如,假设程式中存在以下声明:

然后调用foo(NULL),则foo(int)版本将会被调用,几乎可以肯定这不是程式设计师的意图。

新标準很可能引入一个专用于空指针的关键字,C++11使用nullptr承担这一角色。

nullptr不能被赋给整型,也不能与整型相比较,但它可以向其他指针类型赋值并与之比较。

0的现存角色将会因显而易见的兼容性理由而得到保留。

如果新的语法取得成功,C++委员会可能会将把0和NULL当作空指针的做法标记为已废弃特性,并最终废弃这种双重角色。

在标準C++语言中,枚举不是类型安全的。枚举值实际上就是整数,即便其枚举类型各不相同。这样不同枚举类型的两个枚举值相互比较就成为可能。这方面C++03所提供的唯一安全特性为:一个整数或一种枚举类型的值不能隐式转换为其他枚举类型的值。另外,枚举所使用的整形的大小无法由用户指定,而只能由实现定义。最后,枚举值的作用域为枚举的外围作用域。这就意味着两个不同的枚举不能包含名字相同的成员。

C++0x将允许创建一类没有上述问题的特殊枚举。这种枚举通过enum class来声明:

这种枚举是类型安全的。枚举类的值不会被隐式转换为整数,这样它们也不会被拿来与整数相比较。(Enumeration::Val4 == 101会产生编译错误)

枚举类所使用的类型可以明确指定。预设情况下,如同上例,为int。但这也能像下例那样改变:

这种枚举的作用域也被指定为枚举类名的作用域。使用这种枚举名必须显式指定作用域。Val1无定义,而Enum2::Val1有定义。

另外,C++0x也将允许标準枚举提供其作用域範围以及其使用的整型大小。

这种枚举名被定义具有枚举类型作用域,如(Enum3::Val1)。但是,为了保持向后兼容,枚举名也被放入外围作用域。

枚举的前置声明在C++0x中也将成为可能。以前,枚举类型不能前置声明的理由是枚举的大小取决于其内容。只要枚举值的大小在程式中得以指定,枚举就能前置声明。

标準C++的词法分析器在任何场合下都将">>"解释为右移操作符。然而,在模板定义中,如此解释两个右尖括弧几乎总是错误的。

C++0x将改变词法分析器的规范以便在合理的情况下能把多个右尖括弧解释为模板参数列表的结束符。可以用小括弧来改变这种行为。

标準C++为构造器添加了explicit关键字作为修饰符以防止只有单个参数的构造器使用隐式转换操作。然而,这种做法对于真正的转换操作符是无效的。比如,一个智慧型指针类可能有一个bool()操作符以使自身的行为更像原生指针。如果它包含这种转换操作,它就能用if(smart_ptr_variable)来测试。(若指针非空则测试是为真,否则测试为假。)然而,这种操作也会导致其他出乎意料的转换。由于C++的bool被定义为一种算术类型,这种指针也就可以被隐式转换为整型甚而浮点类型,从而导致一些用户并不希望出现的算术操作。

在C++0x中,explicit关键字将能用于转换操作符的定义中。与构造器一样,它将防止进一步的隐式转换。

在进入这个主题之前,各位应该先弄清楚“模板”和“类型”本质上的不同。class template (类模板,是模板)是用来产生 template class (模板类,是类型)。

在标準 C++,typedef 可定义模板类一个新的类型名称,但是不能够使用 typedef 来定义模板的别名。举例来说:

为了定义模板的别名,C++0x 将会增加以下的语法:

在标準 C++ 中,并非任意的类型都能做为 union 的成员。比方说,带有非预设构造函式的类型就不能是 union 的成员。在新的标準里,移除了所有对 union 的使用限制,除了其成员仍然不能是引用类型。 这一改变使得 union 更强大,更有用,也易于使用。

这一改变仅放宽 union 的使用限制,不会影响既有的旧代码。

在 C++0x 之前, 不论是模板类或是模板函式,都只能按其被声明时所指定的样子,接受一组固定数目的模板实参; C++0x 加入新的表示法,允许任意个数、任意类别的模板实参,不必在定义时将实参的个数固定。

模板类tuple 的对象,能接受不限个数的 typename 作为它的模板形参:

若不希望产生实参个数为 0 的变长参数模板,则可以採用以下的定义:

变长参数模板也能运用到模板函式上。 传统 C 中的 printf 函式虽然也能达成不定个数的形参的调用,但其并非类型安全。 以下的样例中,C++0x 除了能定义类别安全的变长参数函式外,还能让类似 printf 的函式能自然地处理非自带类别的对象。 除了在模板实参中能使用...表示不定长模板实参外,函式实参也使用同样的表示法代表不定长实参。

其中,Params 与 parameters 分别代表模板与函式的变长参数集合, 称之为实参包 (parameter pack)。实参包必须要和运算符“...”搭配使用,避免语法上的歧义。

变长参数模板中,变长参数包无法如同一般实参在类或函式中使用; 因此典型的手法是以递归的方法取出可用实参。

标準C++提供两种字元串常量。第一种,包含在双引号之间,生成一个以'\0'结尾的const char类型的数组。第二种,形为L"",生成一个以L'\0'结尾的constwchar_t类型的数组。这里wchar_t为宽字元。这两种字元串常量均没有对Unicode编码的字元串提供支持。

为了改进C++编译器对Unicode的支持,char类型的定义被修改成除了包含编译器基本执行字元集的所有成员之外还将至少包括UTF-8中的8位编码。以前它只包含前者。

标準C++提供了数种字面值。字元"12.5"是能够被编译器解释为数值12.5的double类别字面值。然而,加上"f"的后置,像是"12.5f",则会产生数值为12.5的float类别字面值。在C++规范中字面值的后置是固定的,而且C++代码并不允许创立新的字面后置。

C++1x (?)开放用户定义新的字面修饰符(literal modifier),利用自定义的修饰符完成由字面值建构对象。

字面值转换可以区分为两个阶段:转换前与转换后 (raw 与 cooked)。 转换前的字面值指特定字元串列,而转换后的字面值则代表另一种类别。 如字面值1234,转换前的字面值代表 '1', '2', '3', '4' 的字元串列; 而转换后,字面值代表整数值1234。 另外,字面值0xA转换前是串列'0', 'x', 'A';转换后代表整数值 10。

C++标準委员会计画为多执行绪提供标準化支持。

有两部分将被涉及:对允许多执行绪在同一个程式中共存的记忆体模型提供定义,对这些执行绪间的互动提供支持。后者将通过类设施来实现。

记忆体模型对于描述多个执行绪在何种情况下可以访问相同的记忆体地址是不可或缺的。

一个遵守相应规则的程式可以保证运行正常,而一个破坏相应规则的程式则可能会因为编译器的最佳化行为以及记忆体一致性等问题而出现不可预料的行为。

在多执行绪环境下,每个执行绪通常都拥有一些自己所独有的变数。对于函式的局部变数来说确实如此,而对于全局以及静态变数来说就不是这样。

除了现存的静态,动态以及自动存储方式以外,在下一个标準中还将提议增加一种局部于执行绪的存储方式。执行绪局部存储方式将通过thread_local存储指示符来指定。

一个本来可能具有静态存储方式的对象(即生命期跨越程式整个执行过程的对象)可能会被赋予执行绪存储方式。与静态存储的对象相似,执行绪局部的对象应该也能用构造器构建并用析构器销毁。

显式使用/不使用C++类的某些(预设)成员函式在标準C++语言中,如果对象自己不提供,编译器会自动产生一个预设构造器,一个拷贝构造器,一个拷贝赋值运算符operator=,以及一个析构器。如前所述,用户可以通过提供自己的版本来覆盖这些预设实现。C++还定义了一些能作用于所有类的全局操作符(如operator=和operator new),当然用户也能覆盖其实现。

问题在于用户对于这些默认产生的函式缺乏控制。例如,如果需要使某个类不可拷贝,用户需要声明一个私有的拷贝构造器,一个拷贝赋值运算符,同时略去这些函式的定义。试图使用这些函式将产生编译错误或连结错误。然而这不是一个理想的解决方案。

而且,对于预设构造器而言,明确告诉编译器产生这类函式通常非常有用。如果对象已经具有了任何构造器,编译器就不会为它提供预设构造器。这在许多场合下的确有用,但有时也需要同时拥有一个编译器产生的预设构造器以及另一个专用构造器。

C++0x将允许用户明确指定使用或不使用这些标準的对象函式。比如,下面这个类型明确声明它会使用预设构造器。

另外,一些特性也能得到明确禁止。比如,下面这个类型是不可拷贝的。

一个类型可以明确禁止用operator new分配:

这个对象只能在栈里面分配或成为其他类型的成员。不使用不可移植的手段将无法在堆中分配该对象。由于使用就地分配new操作符是调用构造器在用户所分配的记忆体中创建对象的唯一途径,并且这一途径已经被上述代码所禁止,可以肯定对象将无法被适当创建。

指示符= delete可被用于禁止调用任意函式,当然它也能用于禁止调用具有某些特定参数的成员函式。比如:

编译器将会拒绝用double值调用f()的企图,而不会无声无息地将其转换为int。这种方法可以被一般化从而禁止使用int以外的任何类型调用该函式,代码如下:

在32位系统中,存在一种至少64位的long long整数类型十分有用。C99标準将此类型引入标準C,大多数C++编译器也早就将它作为一种扩展。事实上,一些编译器在C99引入它很久之前就已经提供支持。C++0x将把这种类型加入标準C++。

C++提供两种方式测试断言,宏assert以及预编译命令#error,但是这两者对于模版来说都不合用。宏在运行期测试断言,而预编译命令则在预编译期测试断言,这时候模版还未能实例化。所以它们都不适合来测试牵扯到模板实参的相关特性。

新的机能会引进新的方式可以在编译期测试assertion,只要使用新的关键字static_assert。 声明採取以下的形式:

允许在没有提供类实例的前提下作用于类的成员,无需明确的对象。

在标準C++,sizeof可以作用在对象以及类别上。但是不能够做以下的事:

这会传回OtherType的大小。C++03并不允许这样做,所以会引发编译错误。C++0x将会允许这种使用。

C++0x不会直接提供透明垃圾收集机制。作为替代,C++0x标準将包含一些有利于在C++实现垃圾收集机制的特性。

对垃圾收集机制的完全支持将会延迟到标準的下一个版本,或者是一个技术报告。

通过对比可以发现,Clang在大多数C++11功能实现上处于领先地位,而Visual Studio则稍显落后。当然,这三个编译器都有着不错的子集适用于跨平台开发。

你可以使用类型推断、移动语义、右值引用、nullptr,static_assert,range-based参考对比。同时你还可以使用最终和重写关键字来进行友好的控制。此外,你还可以通过Enums(例举)强类型和提前声明,这里有几个改进后的模板包括extern keyword。

遗憾的是,Visual Studio并不支持较多请求的可变参数模板。另一方面,可变参数宏在这三款编译器中只支持C99标準。继承构造函式和广义属性这些特性并不是在任何地方都能获得支持。本地执行绪存储是是支持情况最好的一部分(通过非关键字标準)。

C++的同文件混编方式无疑是令人舒畅的。OC/C++混编虽然可以在一个文件中进行编写,但是有一些注意事项需要了解:Objective-C++没有为 OC 类增加 C++的功能,也没有为 C++增加 OC 的功能,例如:不能用 OC 语法调用 C++对象,也不能为 OC 对象增加构造函数和析构函数,也不能将this和self互相替换使用。类的体系结构是独立的,C++类不能继承 OC 类,OC 类也不能继承 C++类。

本文主要就之前令人困惑的 OC 的Block和 C++的lambda混编问题做一些探索。

在深入探索之前,先通过对比的方式了解下二者:

那么是否可以使用__block修改被捕获的 C++变量呢?通过实验发现是可行的。

  1. OC 的Block可以通过引用传递方式捕获 C++对象;
  • OC Block捕获后,weak_count = 2,主要是由于移动构造函数被触发,只是所有权的转移,不会改变引用计数;

了解 C++的同学可能会疑惑,这里既然是移动构造函数被触发,只是所有权发生了转移,意味着monitor作为右值被传递进来,已经变为nullptr被消亡,那么为什么示例中的monitor还可以继续访问?可以来验证一下:

1、当首次执行完如下代码的时候

会发现monitor变量的地址为:

2、当执行block赋值的时候,会调用到std::shared_ptr的移动构造函数中:

3、当执行完block的时候,再次打印monitor的地址,会发现monitor的地址已经发生了变化,和第二步中的this保持了一致,这说明monitor已经变为第二步中的this。

整个过程中,monitor前后地址发生了变化,分别是 2 个不同的std::shared_ptr对象。所以monitor还可以继续被访问。

被捕获的 C++对象何时释放?

同样在 OC 的Block释放的时候,会对其捕获的 C++对象进行释放。

C++的this是一个指针,本质就是一个整数,OC 的Block捕获this和捕获一个整数并没有本质上的区别,所以这里不再详细讨论。这里重点看下 C++的shared_from_this类,它是 this 的智能指针版本。

我要回帖

更多关于 数组可以直接在程序中使用 的文章

 

随机推荐