欢迎您来到芯片的博客!

Welcome to Chipset's blog!

C++ 0x FAQ (翻译版, 更新中...)

经Bjarne Stroustrup博士同意,我翻译了这篇文章。本文很不完整,会随着英文版更新而频繁更新。欢迎转载,转载请注明出处,欢迎指出翻译错误不妥之处。请参考英文版原文http://www.research.att.com/~bs/C++0xFAQ.html

C++0x - the next ISO C++ standard
2009年4月6日更新

该文档由Bjarne Stroustrup 编写和维护,欢迎提出建设性的评论和建议。
C++0x是下一个ISO C++标准,目前有一个用来评论的草案。原来的(和当前的)标准通常指的是C++98或C++03。C++98和C++03之间的差异非常小而且技术性很强 ,应该不会引起用户的特别关注。
预料该标准将会于2009年进行最终全国投票表决 - 即使ISO官方会用一段时间完成它的正式版,但希望能够产生C++09。所以,如果您有任何评论,请找你们国家标准机构的成员们 - 或者标准机构的一个成员 - 发送您的评论。现在这是唯一的可行方案能够确保委员会不必处理很多非常类似的评论。记住:委员会是由志愿者组成的,他们时间和资源有限。
有关C++0x的所有官方文件可以从ISO C++委员会网站上找到,该委员会的官方名称是SC22 WG21。
特别说明:该FAQ将会需要很长一段时间才能编写完毕,您的评论、问题、参考和建议统统欢迎。
请注意这个FAQ的目的不是针对单个特性提供面面俱到的讨论和怎么使用它。目标是通过简单的例子来说明C++0x必须提供些什么。我的理想是不论一个特性多么复杂,”每个特性最多一页”,详细情况通常可以在参考资料中找到。
________________________________________
目的
该C++0x FAQ的目的是
。概览一下C++0x提供的新机制(语言特性和标准库)以及原来的ISO C++标准提供了那些内容。
。指出ISO C++标准努力的目标。
。为深入学习新特性提供参考。
。列出很多做出贡献的个人姓名(大多数是给委员会写报告的作者)。标准并非匿名公司所做。
请注意该FAQ的目的不是针对单个特性提供全面的讨论或者详细讲解怎么使用这些特性。目的是用简单的例子说明C++0x必须提供那些东西(以及参考)。我的想法是不论一个特性多么复杂,”每个特性最多一页”。详细内容一般可以在参考文献中找到。
________________________________________
问题列表
下面是一些高层次的问题。
。你怎么看待C++0x?
。C++0x何时成为一个正式标准?
。什么时候编译器才能实施C++0x?
。何时新标准的库可以投入使用?  
。C++0x将会提供哪些新语言特性? (一个列表);见下面的问题
。C++0x将会提供那些新标准库? (一个列表); 见下面的问题
。C++0x的努力目标有哪些?
。委员会有哪些特别的指导目标?
。我可以从什么地方找到委员会的文章?
。我到哪里去找C++0x的学术和技术文件? (一个列表)
。有没有关于C++0x的视频? (一个列表)
。C++0x难学吗?
。我还可以从其他什么地方阅读有关C++0x的消息? (一个列表)
。委员会是怎么运作的?
。委员会由哪些人组成?
。一个实现应该以什么顺序提供C++0x的这些特性?
。是否会出现C++1x?
关于语言单个特性的问题如下:
。对齐
。属性
。原子操作
。axioms (语义假设)
。auto (来自初始化器的类型推断)
。C99特性
。concepts
。concept maps
。enum类 (受限的强类型enum)
。拷贝和重新抛出异常
。extern 模板
。常量表达式(广义的;constexpr)
。decltype
。defaulted and deleted functions(缺省控制)
。委托构造函数
。并发环境的动态初始化和销毁http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm
。显式转换运算符
。扩展的整型类型 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1988.pdf
。extern模板
。for语句 见range for语句
。后缀返回类型语法 (扩展的函数声明语法)
。类内成员初始化器
。inherited constructors
。初始化器列表 (一致通用的初始化)
。lambda
。局部类作为模板参数
。long long 整数 (至少64位)
。内存模型
。move语义;见右值引用
。namespace关联 (强的 using)
。防止变窄
。零(空)指针 (nullptr)
。POD类型 (广义的)
。range for语句
。原始字符窜
。右尖括号
。右值引用
。静态(编译时)判别 (static_assert)
。模板参数类型检查;见concepts
。template别名
。template typedef; 见template别名
。线程本地存储 (thread_local)
。unicode字符
。一致的初始化语法和语义
。union (广义的)
。自定义literals
。变参数模板
我常常从建议中借用例子,感谢那些提建议的作者们。当然,很多例子来自我自己的演讲和文稿。
有关某个标准库机制的问题如下:
。放弃一个进程 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2440.htm
。algorithm的改进
。数组
。容器 的改进
。日期和时间
。前向列表 单链表
。future and promise
。垃圾收集应用二进制接口
。哈希表;见unordered_map
。shared_ptr
。智能指针;见shared_ptr and unique_ptr
。线程
。原子操作
。tuple
。unique_ptr
。forward_list 单链表
。unordered_map
。regex 一个正则表达式库
。随机数产生器
。scoped allocators
。metaprogramming and type traits
。weak_ptr
下面的是针对上面问题的回答。
________________________________________
你怎么看待C++0x?
这是一个(对于我而言)有点出乎预料的经常被提问的问题。可能是最频繁的被问到的一个问题。对于我而言有点奇怪,C++0x感觉像一种新语言:各个部分比原来更好的组合在一起,我看到了一个比以往更自然的高水平编程风格,却跟原来一样高效。如果你小心翼翼的接近C++仅仅认为它是一个更好的C或者把它当做一种面向对象语言话,那将会走错方向。C++较以往抽象机制更加灵活而且代价更低。依赖于古老的曼佗罗:如果你把它当做一个单独的想法或者对象,直接在程序中表达出来;把现实世界的对象,概念和抽象机制直接模型化为代码。现在就更容易了(你的想法映射成为枚举, 对象、类(例如缺省控制)、类结构(例如inherited constructors)、模板、concepts, concept maps, axioms, aliases, 异常、循环, 线程等等。)
我的理想是让编程语言机制帮助程序员把系统设计和实现从不同角度去考虑。我认为C++0x可以做到这一点 – 不仅仅为了C++程序员,而是为了使用一系列现代编程语言的一般意义上的程序员,而且是非常广阔领域的系统编程。
换句话说,我仍旧是个很乐观的人。
________________________________________
C++0x 何时成为一个正式标准?
正式用于评论的首个草案于2008年9月产生,这也是当前正在接受评论的一个。大约1年后 — 很可能2009年9月 -- 委员会将投票表决出一个最终草案让国家标准机构投票。几个月后,这个最终草案(FDIS)很可能成为新标准,仅仅印刷上不同而已。
所以,确切一点来说,到2009年10月,我们就应该知道新标准是什么样子了。那时,我们再讨论是C++09或者C++10或者C++0A或者 C++0xA或者其它什么名字。我个人倾向于简单通俗一点就用C++,仅仅为了跟原来版本的C++相区分的时候,例如ARM C++, C++98和C++03,则用C++0x。
________________________________________
什么时候编译器才能实施C++0x ?( 符合C++0x 标准的编译器何时出台?)
目前shipping型编译器(例如GCC C++, IBM C++, 和 Microsoft C++) 已经实施了一些C++0x的特性。例如,似乎很明显也很流行把全部或者大部分新标准的库添加上了。
我估计越来越多的特性会在每个新发行的编译器版本中得到实现。很可能,相对单一的特性,例如 auto 、lambda和强类型的 enum ,将会首当其冲。我不介意去猜测何时每一个编译器将提供所有C++0x的特性 -- 毫无疑问,那需要几年时间 -- 但我注意到当每一个C++0x的特性已经被某人于某地实现时,该实现经验就在实现者们之间派上用场了。
________________________________________
何时新标准库可以投入使用?
新标准库的最初版本目前已经被GCC和微软采纳,在boost这里。
________________________________________
C++0x 将会提供哪些新语言特性?
有人认为仅仅添加每个语言特性而不用去改变语言本身是个不错的想法。事实上,基本上多数现代语言的每个特性都曾被某人建议过加入C++,可以想像 C90, C#, Java, Haskell, Lisp, Python,和Ada的超集是个什么样子。记住:使问题变得更棘手的是即使委员会承认某些特性糟糕,但是去除比较古老的这些特性并非实际可行,实际经验 表明,出于兼容性(或默认)原因,几十年来用户一直迫使每位实施者继续提供不宜用和被禁用的特性。
为了从大量建议中选出合理化建议,我们设计了一套特别设计目标。我们不能完全遵循这些,在每个细节上,指导委员会它们也不够全面(在我看来不太可能那么全面)。
结果一种语言的的抽象机制被大幅度的改善了。用C++表达的抽象范围得到了极大的改善,可以更加优雅、灵活、跟手工特化代码相比零开销。当我们谈到”抽象”时,人们常常仅仅想到”类”或”对象”,C++0x远不止这些:通过添加一些特性,例如初始化器列表, 一致的初始化, 模板别名, 右值引用, defaulted and deleted functions, 和变参模板,自定义类型可以简洁和安全的表示了。通过使用概念,它们的通用性以及集成到类型系统的能力得到改善,而且通过一些特性,例如auto, inherited constructors, and decltype,实现起来也变得容易了。更重要的是concepts (包括axioms 和concept maps) 允许我们精确的指定类型需求和类型间的需求。这些扩充足可以让C++0x感觉像一种新语言。
有一个已接受的语言特性列表, 见特性列表。
________________________________________
C++0x 将会提供哪些新标准库?
我宁愿见到更多的标准库。但是,注意到标准库定义已经大约占用了标准中75%的正文篇幅(而且这还不算通过引用包含进来的C标准库)。即使如此,我 们中有些人仍旧乐意见到更多的标准库,没有人说过库工作组懒惰不做事。也应该注意到,通过使用新语言特性,C++98库已经被大幅度的改善了,例如初始化器列表、右值引用、可变参数模板和constexpr。C++0x 标准库易用、安全(更好的类型检查)、比C++98性能更高。
已接受的库有一个列表,见库组件列表, 还有TR2库。
________________________________________
C++0x 的努力目标有哪些?
C++ 是一种大众化的编程语言,也有偏见认为C++属于系统级编程语言,C++是
。一个更好的C
。支持数据抽象
。支持面向对象编程
。支持泛型编程
C++0x的总体努力目标是在以下方面去加强:
。让C++成为一种更好的系统编程和建造库的语言 -- 也就是说直接用C++的现有机制进行编程,而不是针对特殊子领域提供便利设施(例如:数值计算或者窗口应用开发)。
。让C++易教易学 -- 通过改善的一致性,强的保障,以及支持初学者的机制(新手永远比专家多)。
当然,这些都是在非常严格的兼容性约束下完成的。即使增加新的关键字(例如: static_assert , concept , 和 constexpr )会破坏符合标准的代码,但是委员会仅仅在极个别情况下才会那么做。
更多信息见:
。B. Stroustrup: Evolving a language in and for the real world: C++ 1991-2006. ACM HOPL-III. June 2007.
。B. Stroustrup: A History of C++: 1979-1991. Proc ACM History of Programming Languages conference (HOPL-2). March 1993.
。B. Stroustrup: C and C++: Siblings. The C/C++ Users Journal. July 2002.
________________________________________
委员会有哪些特别的指导目标?
当然,有关标准化中的不同人和不同组织目标会有些不同,尤其涉及到细节和主次方面。况且,具体的目标会随着时间而改变。请记住委员会甚至不能做所有 人都赞成认为好的东西 -- 委员会由志愿者组成,资源有限。不过,在讨论什么特性和库应该加入C++0x时,这里有一套实际中遵循的标准:
。保持稳定性和兼容性 -- 不破坏原来的代码,如果必须破坏的话,就不默无声息的这样做。
。增加库好过扩展语言 -- 一个好想法但是委员会做得不是十分尽人意,委员会里的太多人和非委员会的其他很多人对”真实的语言特性”情有独衷。
。通用胜过专一 -- 焦点在于改善抽象机制(类、模板等等)。
。既支持专家也支持初学者 -- 新手通过好的库和更通用的规则获得支持;老手需要通用高效的特性。
。增加类型安全性 -- 主要通过一些便利设施允许程序员避开非类型安全的特性。
。改善性能和直接操作硬件的能力 -- 使C++在嵌入式系统和高性能计算方面更出色。
。融入现实世界 – 考虑工具链、实现代价、转换问题、应用二进制接口问题、教学等等。
注意集成这些特性(新的和旧的)于一身是问题的关键 -- 以及大部分工作。整体绝非各部分的简单相加。
另外一种考察详细目标的方法是看看应用领域和应用风格:
。机器模型和并发 -- 提供强有力的保障和好的机制充分使用现代化硬件(例如:多核和弱一致性内存模型)例如应用二进制接口线程,线程本地存储,和原子应用二进制接口。
。泛型编程 -- 泛型编程是C++98获得巨大成功的案例之一;我们需要在经验的基础上改善支持它。例如auto,concepts,和template别名。
。系统编程 – 改善和支持更接近硬件的编程(例如:低层的嵌入式系统)以及效率。例如: constexpr , std::array ,和通用的POD。
。建造库 -- 从抽象机制中去除限制、低效和非规则。例如:强有力的using, inherited constructors,和右值引用。
________________________________________
我从哪里可以找到委员会的文章 ?
到委员会网站的文件部分去找。那里的细节可能会令你应接不暇。查找 “问题列表”和 “State of”(例如:State of Evolution (July 2008))列表。主要机构是:
。Core (CWG) -- 处理语言技术问题和原理
。Evolution (EWG) -- 处理语言特性建议
。Library (LWG) -- 处理库机制建议
这里是C++0x标准的建议草案。
________________________________________
我到哪里去找有关C++0x的学术性和技术性文章
。Hans-J. Boehm and Sarita V. Adve: Foundations of the C++ concurrency memory model. ACM PLDI'08.
。Hans-J. Boehm: Threads Basic. 没有出版的技术报告。 // 介绍性的内容
。Douglas Gregor, Jaakko Jarvi, Jeremy Siek, Bjarne Stroustrup, Gabriel Dos Reis, Andrew Lumsdaine: Concepts: Linguistic Support for Generic Programming in C++. OOPSLA'06, October 2006. // 从2006年起concept的所设计和实现;从那以后一直在改善
。Douglas Gregor and Jaakko Jarvi: Variadic templates for C++0x. Journal of Object Technology, 7(2):31-51, February 2008.
。Jaakko Jarvi and John Freeman: Lambda functions for C++0x. ACM SAC '08.
。Jaakko Jarvi, Mat Marcus, and Jacob N. Smith: Programming with C++ Concepts. Science of Computer Programming, 2008. To appear.
。M. Paterno and W. E. Brown : Improving Standard C++ for the Physics Community. CHEP'04. // 很多已经又改善了
。Michael Spertus and Hans J. Boehm: The Status of Garbage Collection in C++0X. ACM ISMM'09. To appear.
。Bjarne Stroustrup: Evolving a language in and for the real world: C++ 1991-2006. ACM HOPL-III. June 2007. (incl. slides and videos). // 概括C++0x的设计目的,标准化进程,以及2007年前的发展过程。
。Anthony Williams: Simpler Multithreading in C++0x. devx.com.
该列表不太全面 – 伴随着人们写新文章越来越旧。如果你发现有的文章应该出现在这里,请发表它。而且,并非所有的文章都随着标准的改进而保持最新状态。我尽力让评论保持最新。
________________________________________
我还可以在哪里阅读到C++0x的信息?
随着标准接近完成以及C++实现提供新语言特性的库,C++0x的信息剧增。这里有一个简短的资源列表:
。委员会网站的文章部分。
。C++0x草案。
。the C++0x Wikipedia entry。尽管不是委员会成员在维护,但似乎一直在更新。
。GCC的试验性实现C++0x特性支持网页。
________________________________________
有没有C++0x的视频?
(对于那些了解我的人来说,这确实是一个FAQ,但不是我个人喜欢的问题;我不是技术论题视频的爱好者 – 我觉得视频分心而且口头描述容易含有细微的技术性错误)。
是的:
Try Google videos.
。Stroustrup at U of Waterloo on C++0x in 2007.
。Stroustrup on initializer lists at Google in 2007.
。Larry Crowl on Threads.
。Roger Orr on C++0x in January 2008.
。Hans Boehm on Getting C++ Threads Right in December 2007.
________________________________________
C++0x难学吗?
既然我们不能在不破坏大量代码前提下去掉任何重要的C++特性,C++0x比C++98要大,假如你想知道每个规则,学习C++0x可能难一点。这样我们会有两个工具去简化(从学习者角度看):
。一致通用:取代,也就是说三个规则加一个通用规则(例如,一致的初始化,继承的构造函数,和 线程)。
。简单备用:提供了比原来更易用的新机制(例如,数组 和regex 库以及 range for语句,受限模板,和regex语言特性)。
显而易见,”从头开始”教/学的方式不占什么优势,而且当前(明显)几乎没有什么资料采用其它方法。不过这会随着时间的推移而改变。
________________________________________
委员会是怎么运作的?
ISO标准委员会和SC22 WG21在ISO为委员会订立的规定下运作。奇怪的是,这些规定没有标准化而且会随着时间稍有变化。
很多国家有国家标准机构,其中有活跃的C++团队。这些团队开展会议,网络上互相协作并向ISO会议派遣代表。加拿大、法国、德国、瑞士、英国和美国在多数会议中均有列席。丹麦、荷兰、日本、挪威、西班牙和其他一些国家时不时的有个人代表出席。
大多数工作通过网络会议进行,讨论结果以文件形式记录下来,文件以会议次数命名并发布在WG21 网站上。
委员们每年碰面二至三次,每次一周时间。这些会议上的多数工作在子工作组开展,例如“核心”、“库”、“演化”,和“并发”。根据需要,也组织特定 紧急议题的特别工作组之间的会议,例如“concepts”和“内存模型”。主要会议上会投票。首先,工作组拿“原始票”查看一个议题是否提交给委员会的 所有准备工作已经就绪。接下来,如果某个议题已经接受国家投票,委员会就全体投票(每个委员一票)。我们会非常小心,为了确保不至于陷入大多数委员到场而 国家标准机构不赞成的局面 -- 如果出现这种情况会引发长时间的争论。官方草案的最终投票由国家标准机构以邮件的形式进行。
委员会和C标准机构(SC22 WG14)以及POSIX都有正式联络,并或多或少的跟其他几个机构正式接触。
________________________________________
委员会由哪些人组成 ?
委员会有一大批人(大约200)组成,其中大约60人在每年二至三次的长达一周的会议上列席。此外,还有国家标准机构和以及相应会议。多数成员通过 参加会议、email讨论或者提交文稿给委员会考虑。多数成员有同事和朋友的帮助他们。从1号开始,委员会的成员就会来自很多国家,并且每次会议都会有来 自6~12个国家的人参加。最终的投票由大约20个国家机构来表决。可以看出,ISO C++标准化做出相当大的努力,而不是一小股相干人士为了“人们仅仅喜欢他们”而创作一种优秀的语言。标准就是这些志愿者们竭尽所能制造出来认为能让所有 人都能使用的最好的东西。
当然,很多(不是所有)志愿者都有白天焦点在C++上的工作:我们有编译器编写人员、工具编写人员、库编写人员、应用开发人员(为数很少)、研究人员(仅仅几个)、顾问、测试套件编写人员,还有更多。
这里有一个有关的缩写组织列表:Adobe, Apple, Boost, EDG, Google, HP, IBM, Intel, Microsoft, Red Hat, Sun。
这里有一个简短的成员名字列表,您可能在文献或者网上见到过他们:Dave Abrahams, Matt Austern, Pete Becker, Steve Clamage, Lawrence Crowl, Beman Dawes, Doug Gregor, Howard Hinnant, Jaakko Jarvi, Francis Glassborow, Jens Maurer, Jason Merrill, Sean Parent, P.J. Plauger, Tom Plum, Gabriel Dos Reis, Bjarne Stroustrup, Herb Sutter, David Vandervoorde. 抱歉,未能列出当前200个之外和过去的成员。
通过检查WG21文件列表的作者,您可以获得一个有关专家广度和深度的较好印象,但是请记住有对标准的努力作出主要贡献的人并没有写太多东西。
________________________________________
一个实现应该以什么顺序提供 C++0x 的这些特性?
标准(十分确凿地)说 跟实现顺序无关,为了完全一致,标准仅仅列出所有需要做的东西。但是,我们会有些看法,只要我们想当然实现者们会分阶段添加新特性就感觉有顺序了。毕竟, 我不能使用一种我需要但所有实现都不支持的特性。于是,对于早期的实现,就有这么一种基于“容易提供”和“对很多人有用”观点(仅仅是观点)成了关键准则。
。新库中没有什么特性依赖于语言机制(例如变参数模板和 constexpr )
。简单的不能再简单的实现特性虽然在很小的方面却很明显为使用者们提供了方便:
。auto
。enum 类
。long long
。nullptr
。右括弧
。static_assert
。帮助实现C++0x标准库的语言特性(能升级/改善标准库):
。constexpr
。初始化列表(通用一致的初始化)
。右值引用
。变参数模板
。和并发控制有关的特性:
。内存模型
。thread_local
。原子类型
。局部类型作为模板参数
。lambda
。POD(一般性的)
。和concept相关的特性:
。concepts
。range for
。标准库中的concepts
如果你看得很仔细,你会注意到很多语言特性我没有发表什么看法(根据时间先后介绍)。当然,我也希望别人尽可能而且尽快如此,我并没有给出一个逻辑 上时间顺序的论断。很显然,每一个C++实现的提供者有他们自己的优先级,所以我们不能指望他们按部就班,但是我们希望他们注意点其他人正在做什么,以便 为用户早一点提供更多的可移植性。
________________________________________
是否会出现C++1x?
很可能 -- 不仅仅是委员会拖延了C++0x的截止期限。我常常听到这样一个希望/计划,委员会在C++0x投票后应该立即开始着手C++1x,在C++1x出台之前努力一段时间。两个标准间隔十年对于当前技术的步伐来说太长了,于是有些人梦想版本之间3年时间。我个人认为5年比较现实;C++15或别的?________________________________________
auto -- 从初始化器诊断类型
看看
auto x = 7;
这里 x 将会具有类型 int , 因为初始化常量的类型是 int 。一般而言,我们可以这样写
auto x = 表达式;
x 值的类型来自“表达式”的类型。
当一个变量的类型不太容易知道或者不容易书写的时候,用 auto 从初始化器诊断一个变量的类型很有意义。考虑一下:
template<class T> void printall(const vector<T>& v)
{
for (auto p = v.begin(); p!=v.end(); ++p) cout << *p << "\n";
}
在C++98中,我们不得不这样写
template<class T> void printall(const vector<T>& v)
{
for (typename vector<T>::const_iterator p = v.begin(); p!=v.end(); ++p)
cout << *p << "\n";
}
当一个变量的类型极度依赖于模板参数的时候,如果没有 auto 代码真的就不好写。例如:
template<class T, class U> void (const vector<T>& vt, const vector<U>& vu)
{
// ...
auto tmp = vt[i]*vu[i];
// ...
}
tmp 的类型依赖于一个 T 类型和 U 类型变量的相乘, 但是具体是什么类型阅读的人不容易得知,不过编译器曾经处理过,当然知道 T 和 U 是什么具体类型。
auto 特性最早就被建议和实现过:早在1984年在我的Cfront实现中就有了,但是迫于跟C兼容问题而被迫剔除。这些兼容性问题伴随着C++98和C99同意去掉“隐式int”就不复存在了,也就是说两种语言要求每个变量和函数必须显式的定义类型。古老意义上的 auto (“这是一个局部变量”)就成了多于和没有用的东西。几个委员从数百万源代码海底捞针找出几个用的地方 -- 而且多数在测试表中找到或者说是很明显的bug。
还有
。C++草案部分???
建议
________________________________________
Concepts
"Concepts"是一种机制,该机制用于描述一般类型的条件、混合类型的条件以及混合类型。它在早期检查使用模板方面很有用,而且它也能帮助早期发现模板主体内的错误。看一下标准模板库中的算法 fill :
template<ForwardIterator Iter, class V>    // 类型的类型
requires Assignable<Iter::value_type,V>  // 参数类型之间的关系
void fill(Iter first, Iter last, const V& v); // 仅仅是一个声明,不是定义
 
fill(0, 9, 9.9); // Iter是int类型;error: int不是ForwardIterator
                 // int没有一个前缀*
fill(&v[0], &v[9], 9.9); // Iter是int类型;ok: int* 是ForwardIterator
 
注意,我们仅仅声明 fill() ,并没有定义它(提供它的实现)。另外,我们显式的说明了fill()从它的参数中需要什么:
。参数 first 和 last 必须是一种 ForwardIterator 类型(并且它们的类型必须相同)。
。第三个参数 v 必须是一种可以给 ForwardIterator 的 value_type 赋值的类型。
当然,读了标准后我们就会知道这些了。但是编译器可不会阅读标准文档,我们不得不以代码的形式告诉它我们使用了 ForwardIterator 和 Assignable concepts。结果在使用fill()时的错误就会马上被捕捉到,出错信息可读性也能有很大改善。一般而言,编译器现在有了程序员意图的信息用于较好的检查和诊断。
Concepts对于template实现者也有裨益。看一看:
template<ForwardIterator Iter, class V>
requires Assignable<Iter::value_type,V>
void fill(Iter first, For Iter, const V& v)
{
while (first!=last) {
   *first = v;
   first=first+1;       // error: Forward_iterator没有定义+
                            // (使用++first)
}
}
这个错误会马上被捕捉到,从而无须太多繁杂的测试(尽管不需要测试全部)。
注意到我们说过 int* 是 ForwardIterator ,也讨论了 ForwardIterator 的 value_type 。但是一个 int* 并没有一种称谓 value_type 的 成员;事实上它没有成员。那么 int* 怎么会是一种 ForwardIterator 呢?因为我们说它是。使用一个 concept_map ,在 ForwardIterator 需要的地方我们说何时用 T* ,我们考虑 T 的 value_type :
template<Value_type T>
concept_map ForwardIterator<T*> {  // T*的value_type是T
typedef T value_type;
};
一个 concept_map 允许我们说怎么去看到一个类型,避免了不得不去改变它或者把它包装成一种新类型。
为了能够归类和区分不同的类型的类型,我们可以传递的类型种类进行重载。例如
// 基于iterator的标准排序(有concepts):
template<Random_access_iterator Iter>
requires Comparable<Iter::value_type>
void sort(Iter first, Iter last); // 使用常规的实现
 
// 基于容器的排序:
template<Container Cont>
requires Comparable<Cont::value_type>
void sort(Cont& c)
{
sort(c.begin(),c.end());    // 仅仅调用不同版本的iterator就可以了
}
 
void f(vector<int>& v)
{
sort(v.begin(), v.end());   // 一种方法
sort(v);                    // 另外一种方法
// ...
}
你可以定义自己的concept,不过标准库为新手提供了多种有用的concept,例如 ForwardIterator,Callable,LessThanComparable,和 Regular 。
备注:C++0x标准库规定使用concept。
同时看看
。C++草案部分???
。建议。
。Douglas Gregor, Jaakko Jarvi, Jeremy Siek, Bjarne Stroustrup, Gabriel Dos Reis, Andrew Lumsdaine: Concepts: Linguistic Support for Generic Programming in C++. OOPSLA'06, 2006年10月。
________________________________________
Concept maps
int*是一个ForwardIterator;当代表concept时我们这样说,标准也一直这样说,即使第一个版本的STL也把指针当做迭代器。不过我们也谈论了ForwardIterator的value_type。但是一个int*并没有一个叫做value_type的成员;事实上它没有成员。那么一个int*怎么会是一个ForwardIterator呢?因为我们说它是。使用concept_map,当需要ForwardIterator 时用T*,我们把T当做value_type:
template<Value_type T>
concept_map ForwardIterator<T*> {      // T*的value_type是T
typedef T value_type;
};
concept_map允许我们描述希望如何看待一个类型,从而不必改变或者把它包装成一种新类型。"Concept maps"是为适应独立开发常规应用软件而设立的一种非常灵活和通用的机制。
再看看
。C++草案部分???
。建议。
。Douglas Gregor, Jaakko Jarvi, Jeremy Siek, Bjarne Stroustrup, Gabriel Dos Reis, Andrew Lumsdaine: Concepts: Linguistic Support for Generic Programming in C++. OOPSLA'06, October 2006.
________________________________________
Axiom
axiom是指定一个概念语义的一套谓词。axiom主要用于外部工具(非通常意义上的编译器行为),例如特定域优化工具(指定程序转化的语言就是axiom一个很重要的动机)。第二个应用是标准中简单精确的语义条款(用于标准库条款中很多部分)。Axiom也可能用于一些优化(通过编译器和传统优化器完成),但编译器并不需要注意用户提供的axiom;它们工作在基于标准定义的语义上。
一个axiom计算的列表对可以认为相等。考虑:
concept Semigroup<typename Op, typename T> : CopyConstructible<T> {
T operator()(Op, T, T);
axiom Associativity(Op op, T x, T y, T z) {
op(x, op(y, z)) <=> op(op(x, y), z);   
// T的运算符可以假设为相关联
}
}

concept Monoid<typename Op, typename T> : Semigroup<Op, T> {
// 一个独异点是带有身份元素的半群
T identity_element(Op);
axiom Identity(Op op, T x) {
op(x, identity_element(op)) <=> x;
op(identity_element(op), x) <=> x;
}
}
<=>是等价运算符,仅仅用于axiom中。注意你不能(通常意义上)证明一个axiom;我们使用axiom去陈述我们不能证明的东西,但一个程序员可以陈述一个可接受的假设。注意对于某些值而言,等价语句两侧可能非法,例如使用NaN (Not a Number)用于浮点类型:如果等式两侧都使用NaN二者(明显)非法并相等(独立于axiom的描述),除非一边使用了NaN 才可能有axiom优势。
axiom是一个等价语句序列(用<=>)或条件语句(格式"如果(something) 那么我们可以假设下列等价"):
// 在concept TotalOrder中:
axiom Transitivity(Op op, T x, T y, T z)
{
if (op(x, y) && op(y, z)) op(x, z) <=> true; // 调价等价
}
See also
。C++草案 14.9.1.4 Axioms
。???.
________________________________________
Range for语句
一个range for语句允许你迭代整个Range, 它是一个concept,能够让你可以像用STL序列一样定义begin()和end()而迭代的东西。所有的标准容器都可以当做一个range使用,像std::string,初始化器列表,数组,任何你定义begin()和end()的东西,例如istream。例如:
void f(const vector<double>& v)
{
for (auto x : v) cout << x << '\n';
for (auto& x : v) ++x;   // 用引用允许我们去修改它的值
}
你可以读作"对于v中的所有x"从v.begin()一直迭代到v.end()。再如:
for (const auto x : { 1,2,3,5,8,13,21,34 }) cout << x << '\n';
再看看
。C++草案6.5.4
。Wording for range-based for-loop (revision 2)。
________________________________________
右尖括弧
考虑
list<vector<string>> lvs;
在C++98中,这是一个语法错误,因为在两个>之间没有空格。C++0x把这样的>当做两个模板参数列的正确结束标示。
为什么这在原来是一个问题呢?编译器前端用分析阶段组织。这关系到一个最简单的模型:
。词法分析lexical analysis (从字符中抽取词元)
。语法分析syntax analysis (检查语法)
。类型检查type checking (查找名称和表达式类型)
理论上和有时实际应用中这些阶段严格分开,以至于词法分析器把>>当做一个词元(常常表示右移或输入)不知道它什么意思;特殊情况下,它不知道模板或者内嵌模板参数。然而,让这个例子”正确”就需要三个阶段某种程度上的合作。关键性发现让每个C++编译器已经意识到问题所在从而能够给出合理的错误信息。
再看看
。C++草案部分???
。改进右键括弧建议 。
________________________________________
defaulted and deleted functions -- 缺省控制
通常意义上的“禁止拷贝”用语现在能直接表示了:
class X {
// ...
X& operator=(const X&) = delete; // 不允许拷贝
X(const X&) = delete;
};
相反的情况,我们也能明确的指明想要缺省拷贝行为:
class Y {
// ...
Y& operator=(const Y&) = default; // 缺省拷贝语义
Y(const Y&) = default;
};
关于显式的缺省很明显是多此一举,但是关于此效果的评论以及(糟糕的)使用者显式的给defaulted行为定义拷贝操作并非不常见。把此缺省行为的工作留给编译器实现更简单、不容易出错,而且通常生成的目标代码更好。
该“defaulted”机制可以用于任何具有缺省行为的函数。该“deleted”机制可以用于任何函数。例如,我们可以类似如下这样禁止一个不希望的转换:
struct Z {
// ...
 
Z(long long);     // 可以用一个long long的值初始化
Z(long) = delete; // 但不时任何but not anything less
};
再看看
。C++草案部分???
。改进建议。
________________________________________
class enum -- 受限的强类型enum
class enum (“新enum”, “强enum”)指出了传统C++枚举存在三个问题:
。当某人不希望一个枚举表现整数行为的时候,传统的 enum 隐式转换成 int 会引发错误。
。传统的 enum 把枚值权限蔓延到周围区域导致名字冲突。
。一个 enum 下层的类型不能指定,造成混乱、相容问题,并且导致无法前向声明。
enum class (“强enum”)是强类型而且有作用域:
enum Alert { green, yellow, election, red }; // 传统的enum
enum class Color { red, blue };
enum class TrafficLight { red, yellow, green };
 
Alert a = 7;              // error (和原来一样)
Color c = 7;              // error: 无int->Color转换
 
int a2 = red;             // ok: Alert->int转换
int a3 = Alert::red;      // C++98中error; C++0x中ok
int a4 = blue;            // error: 找不到blue
int a5 = Color::blue;     // error: 无Color->int转换
 
Color a6 = Color::blue;   // ok
由此可见,传统的 enum 如以前一样,但是现在你可以用 enum 名字随意限定。
能够指定下层的类型允许简单交互、确保了枚举的大小、以及前向声明:
enum class Color : unsigned int { red, blue };
enum class TrafficLight { red, yellow, green };  // 缺省状态,下层类型是int
enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U };   // E多大?
enum class Color_code : char;                    // (前向)声明
void foobar(Color_code* p);
// ...
enum class Color_code : char { red, yellow, green, blue }; // 定义
再看一看
。C++草案部分???
。改进enum建议。
。前向声明建议。
________________________________________
constexpr – 通用常量表达式
constexpr 机制
。提供更通用的常量表达式
。允许常量表达式关联自定义类型
。提供一种方法保证初始化在编译时完成
考虑一下
enum Flags { good=0, fail, bad, eof };
constexpr int operator|(Flags f1, Flags f2) { return Flags(f1|f2); }
void f(Flags x)
{
switch (x) {
case bad:    /* ... */ break;
case eof:    /* ... */ break;
case bad|eof: /* ... */ break;
default:     /* ... */ break;
}
}
这里的 constexpr 指出,如果给出常量表达式,那么函数必须是能够编译时计算出其值的简单形态 。
除了在编译时能够计算出表达式的值外,我们还希望 要求 编译时就能计算出表达式的值; 把 constexpr 置于一个变量前面可以做到(并暗含 const ):
constexpr int x1 = bad|eof;        // ok
void f(Flags f3)
{
constexpr int x2 = bad|f3;       // error: 编译时不能计算出其值
int x3 = bad|f3;                 // ok
}
一般而言,我们希望编译时计算出其值可用于全局和namespace对象,常常用于我们想要存放的只读存储对象。
当一个对象的构造函数足够简单可以成为 constexpr 并且关联了该对象的表达式:
struct Point {
int x,y;
constexpr Point(int xx, int yy) : x(xx), y(yy) { }
};
 
constexpr Point origo(0,0);
constexpr int z = origo.x;
再看一看
。C++草案部分???
。改进constexpr建议。
________________________________________
decltype -- 一个表达式类型
decltype(E) (“声明的类型”)是一个可以用于声明中的类型名或者表达式 E 。例如:
void f(const vector<int>& a, vector<float>& b)
{
typedef decltype(a[0]*b[0]) Tmp;
for (int i=0; i<b.size(); ++i) {
Tmp* p = new Tmp(a[i]*b[i]);
// ...
}
// ...
}
这种念头在泛型编程中冠以“typeof”流行了很长时间,但是 typeof 实现在实际应用中不够全面而且不相容,所以标准的版本就命名为 decltype 。
再看看
。C++草案部分???
。改进decltype建议。
________________________________________
初始化器列表
考虑一下
vector<double> v = { 1, 2, 3.456, 99.99 };
list<pair<string,string>> languages = { {"Nygaard","Simula"}, {"Ritchards","BCPL"}, {"Ritchie","C"} };
map<vector<string>,vector<int>> years = {
{ {"Maurice","Vincent", "Wilkes"},{1913, 1945, 1951, 1967, 2000} },
{ {"Martin", "Ritchards"} {1982, 2003, 2007} },
{ {"David", "John", "Wheeler"}, {1927, 1947, 1951, 2004} }
};
初始化器列表不再仅仅用于数组。用于接受(随意长度,同一类型 T )一个{}-列表的机制是一个函数,该函数接受一个 std::initializer_list<T> 类型的参数(通常是构造函数)。例如:
void f(initializer_list<int>);
f({1,2});
f({23,345,4567,56789});
f({});    // 空list
f{1,2};    // error: 函数调用()丢掉了

years.insert({{"Bjarne","Stroustrup"},{1950, 1975, 1985}});
初始化器列表可以任意长,但必须一致(所有的元素都是模板类型T,或者可以转化成T)。
一个容器可能像下面这样实现一个初始化器列表构造函数:
template<class E> class vector {
public:
vector (std::initializer_list<E> s) // 初始化器列表构造函数
  {
reserve(s.size());    // 得到正确的空间大小
uninitialized_copy(s.begin(), s.end(), elem); // 初始化元素(在elem[0:s.size())中)
sz = s.size();    // 设置vector大小
  }
      
// ... 跟原来一样 ...
};
直接初始化和拷贝初始化的不同在于{}初始化,但由于{}初始化的缘故而变得不那么特别相关。例如, std::vector有一关于int类型的explicit构造函数和一个初始化器列表构造函数:
vector<double> v1(7);    // ok: v1有7个元素
v1 = 9;            // error: 没有int到vector的转换
vector<double> v2 = 9; // error: 没有int到vector的转换

void f(const vector<double>&);
f(9);                // error: 没有int到vector的转换

vector<double> v1{7};    // ok: v1有1个元素(该元素值是7)
v1 = {9};    // ok v1现在有1个元素(该元素值是9)
vector<double> v2 = {9};    // ok: v2有1个元素(该元素值是9)
f({9});            // ok:通过列表{ 9 }调用f

vector<vector<double>> vs = {
vector<double>(10),     // ok: 显式构造(10个元素)
vector<double>{10},    // ok: 显式构造(1个元素,其值为10)
10     // error: vector的构造函数是显式的
};   
函数可以作为当做一个序列对initializer_list进行存取。例如:
void f(initializer_list<int> args)
{
    for (auto p=args.begin(); p!=args.end(); ++p) cout << *p << "\n";
}
一个接受单个 std::initializer_list 类型参数的构造函数叫做初始化器列表构造函数。标准库容器都有初始化列表器构造函数。
标准库的容器, string, 和regex都有初始化器列表构造函数,赋值运算符等。一个初始化器列表可以作为一个Range使用,例如,像在range for语句里那样
初始化器列表是一致通用初始化模式的一部分。
再看看
。C++草案部分???
。建议。
。最终建议。
________________________________________
防止变窄
问题:C和C++隐式的截短
int x = 7.3;        // Ouch!
void f(int);
f(7.3);            // Ouch!
但在C++0x里,用{}初始化不会变窄:
int x1 = {7.3};    // error: 变窄
double d = 7;
int x2{d};        // error: 变窄(double到int)
char x3{7};        // ok: 即使7是int,也不是变窄
vector<int> vi = { 1, 2.3, 4, 5.6 };    // error: double到int变窄
通过依赖值(例如上面例子中的7),C++0x避免了一些能匹配却不匹配的情况(而且不仅仅是类型)。如果一个值能够恰好代表了目标类型,转换就不会变窄。注意,浮点数到整数的转换永远被认为是变窄-- 即使从7.0到7也不例外。
See also
。C++草案8.5.4.
。建议
。改进的建议 (主要在于"explicit")。
________________________________________
委托构造函数
在C++98里,如果你希望两个构造函数做同样的事,你自己重复调用一个"init()函数"。例如:
class X {
int a;
validate(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }
public:
X(int x) { validate(x); }
X() { validate(42); }
X(string s) { int x = lexical_cast<int>(s); validate(x); }
// ...
};
冗余掩盖了可读性,而且重复容易出错。二者都是程序维护的绊脚石。于是,在C++0x里面,我们可以定义一个构造函数而不用再定义另一个:
class X {
int a;
public:
X(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }
X() :X{42} { }
X(string s) :X{lexical_cast<int>(s)} { }
// ...
};
再看看
。见C++草案部分12.6.2
。N1986==06-0056 Herb Sutter and Francis Glassborow: Delegating Constructors (revision 3). 最终建议。
________________________________________
类内成员初始化器
C++98规定, 仅static const 整型成员可以在类内初始化,而且初始化器必须是常量表达式。这些限制条件保证了我们在编译时完成初始化工作。例如:
int var = 7;
 
class X {
static const int m1 = 7;         // ok
const int m2 = 7;                // error: 非static
static int m3 = 7;               // error: 非const
static const int m4 = var;       // error: 初始化器非常量表达式
static const string m5 = "odd";  // error: 非整型
// ...
};
基本想法是允许类内非静态数据成员声明时初始化,如果需要运行时初始化则用一个构造函数完成该项工作。考虑:
class A {
public:
int a = 7;
};
等同于:
class A {
public:
int a;
A() : a(7) {}
};
这会节约一点文字打字,但真正受益的地方在于有几个构造函数的类。常常所有的构造函数使用一个成员作为普通的初始化器:
class A {
public:
   A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {}
   A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {}
   A(D d) : a(7), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {}
   int a, b;
private:
HashingFunction hash_algorithm;  // 所有A的实例都用hash加密
std::string s;                   // 字符窜指明生存期中对象的状态
};
事实情况是每个缺省值在杂乱的代码中找不到了,这在维护阶段很容易成为问题。作为替换,我们可以单独列出初始化的数据成员:
class A {
public:
A(): a(7), b(5) {}
A(int a_val) : a(a_val), b(5) {}
A(D d) : a(7), b(g(d)) {}
int a, b;
private:
HashingFunction hash_algorithm{"MD5"};  // 所有A的实例都用hash加密
std::string s{"Constructor run"};       // 字符窜指明生存期中对象的状态
};
如果一个成员既用类内初始化器初始化也用构造函数初始化,仅仅构造函数的初始化有效 (它“覆盖”缺省值)。所以,我们还可以进一步简化:
class A {
public:
A() {}
A(int a_val) : a(a_val) {}
A(D d) : b(g(d)) {}
int a = 7;
int b = 5;
private:
HashingFunction hash_algorithm{"MD5"};  // 所有A的实例都用hash加密
std::string s{"Constructor run"};       // 字符窜指明生存期中对象的状态
};
再看看
。C++草案部分???
。建议。
________________________________________
inherited constructors
人们常常为类成员的一般性范围规则感到困惑。尤其,一个基类成员跟派生类中的成员不在同一范围的情况:
struct B {
void f(double);
};

struct D : B {
void f(int);
};

B b;   b.f(4.5);    // 可以
D d;   d.f(4.5);    // 奇怪:用参数4调用f(int)
在C++98里,我们可以把一套基类的重载函数"提升"到派生类里面:
struct B {
void f(double);
};

struct D : B {
using B::f;     // 让所有B里面的f()当前范围内可见
void f(int);    // 添加一个新f()
};

B b;   b.f(4.5);    // 可以
D d;   d.f(4.5);    // 可以: 调用D::f(double)其实是调用的B::f(double)
我曾经说过"仅仅是一个历史的偶然事件阻止了这么用于构造函数和普通成员函数"。C++0x 提供了这种机制:
class Derived : public Base {
public:
using Base::f;    // 把Base的f提升到Derived下 -- C++98中可以这样做
void f(char);     // 提供一个新的f
void f(int);      // 匹配这个f而不是Base::f(int);

using Base::Base; // 把基类的构造函数提升到Derived下 – 仅仅C++0x可以这样
Derived(char);    // 提供了一个新构造函数
Derived(int);     //  匹配这个构造函数而不是Base::Base(int);
// ...
};
如果你如此坚决,你仍然可以在派生类中用inherited constructors,在这里你定义了新的需要初始化的成员变量:
struct B1 {
B1(int) { }
};

struct D1 : B1 {
using B1::B1; // 隐式声明D1(int)
int x;
};

void test()
{
D1 d(6);    // Oops: d.x没有初始化
D1 e;        // error: D1没有构造函数
}
再看看
。C++草案部分12.9.
。建议。Alisdair Meredith, Michael Wong, Jens Maurer: Inheriting Constructors (revision 4).
________________________________________
静态(编译时)判别 -- static_assert
静态(编译时)判别包含了一个常量表达式和一个字符窜常量:
static_assert(expression,string);
编译器估算这个表达式,如果表达式结果为假(也就是判别失败),则按照字符窜给出出错信息。例如:
static_assert(sizeof(long) >= 8, "64-bit code generation required for this library.");
struct S { X m1; Y m2; };
static_assert(sizeof(S)==sizeof(X)+sizeof(Y),"unexpected padding in S");
static_assert可以通过显示的指使编译器做程序假设和处理。注意,既然static_assert在编译时估值,它不能用于依赖于运行时检查的假设。例如:
int f(int* p, int n)
{
static_assert(p==0,"p is not null"); // error: static_assert()表达式非常量
// ...
}
(相反,测试,失败时抛出异常)。
再看看
。C++草案7 [4].
。建议: ???
________________________________________
long long – 一个更长的整数
一个整数至少64位长。例如:
long long x = 9223372036854775807LL;
没有 long long long , long 也不能拼写成 short long long .
再瞧瞧
。C++草案部分???
。short建议。
________________________________________
nullptr -- 一个null 指针标示
nullptr 是一个给null指针的标示;它不是一个整数:
char* p = nullptr;
int* q = nullptr;
char* p2 = 0;           // 0仍然可以,而且p==p2
 
void f(int);
void f(char*);
 
f(0);         // 调用f(int)
f(nullptr);   // 调用f(char*)
 
void g(int);
g(nullptr);       // error: nullptr不是int
int i = nullptr;  // error: nullptr不是int
再瞧瞧
。C++草案部分???
。改进建议
________________________________________
后缀返回 类型语法
Consider:
template<class T, class U>
??? mul(T x, U y)
{
return x*y;
}
我们写什么返回类型?是“ x*y 的类型”,当然,但我们先看看第一个想法,使用decltype:
template<class T, class U>
decltype(x*y) mul(T x, U y) // 作用域问题!
{
return x*y;
}
这不可行,因为 x 和 y 在那个范围不可见。但是,我们可以这样写:
template<class T, class U>
decltype(*(T*)(0)**(U*)(0)) mul(T x, U y)   // 代码丑陋!容易引发错误。
{
return x*y;
}
称之谓“不雅观”是过分客气。
解决办法是把返回类型放到该放的地方,在参数后面:
template<class T, class U>
auto mul(T x, U y) -> decltype(x*y)
{
return x*y;
}
我们使用 auto 来表示后来推断/指定。事实上,该例子应该缩减成这样
template<class T, class U>
auto mul(T x, U y)
{
return x*y;
}
不过,这仍然有待讨论。
后缀语法主要不是关于模板和类型推断的,实际上关于作用域。
struct List {
struct Link { /* ... */ };
Link* erase(Link* p);   // 删除p,并且在p前返回Link
// ...
};
 
List::Link* List::erase(Link* p) { /* ... */ }
第一个 List:: 是必须的,因为在遇到第二个 List:: 之前,还没有进入 List 的作用域。更好一点:
auto List::erase(Link* p) -> Link* { /* ... */ }
现在两个 Link 都不需要显式指定。
再看看
。C++草案部分???
。wording.
________________________________________
template 别名 ( 正式的是"template typedef")
我们怎么做才能让一个模板“看上去像另外一个模板”,只不过可能指定(约束)两三个模板参数?考虑一下:
template<class T>
using Vec = std::vector<T,My_alloc<T>>;  // 标准vector使用了我的分配器
Vec<double> fib = { 1, 2, 3, 5, 8, 13 }; // 用My_alloc分配元素
vector<int,My_alloc<int>> verbose = fib; // verbose和var类型相同
关键字 using 用来获取一个线形标志“名字后面是它的指代对象”。我们尝试了常规的和迂回的 typedef 方法,但是都未能获得一个完全清晰的方案,直到我们在一个不太酶涩的语法上敲定了。
特化工作(您能别名一套特化但不能特化一个别名)。例如:
template<int>
struct int_exact_traits {// idea: int_exact_traint<N>::type 是一个N位长的类型
typedef int type;
};
template<>
struct int_exact_traits<8> {
typedef char type;
};
template<>
struct int_exact_traits<16> {
typedef char[2] type;
};
// ...
template<int N>
using int_exact = typename int_exact_traits<N>::type;  // 为方便表示定义别名
int_exact<8> a = 7;       // int_exact<8> 就是一个8位长的int
再看看
。C++草案部分???
。改进建议。
________________________________________
变参模板
需要解决的问题:
。怎么用1, 2, 3, 4, 5, 6, 7, 8, 9, 或初始化器构造一个类?
。怎么避免用多部分构造一个类再拷贝结果?
。怎么构造一个tuple?
最后的问题是关键:考虑tuple!你可以加工和存取普通的tuple,下面继续。
这里有一个例子(取自”A brief introduction to Variadic templates”(见引用)) 实现一个通用,安全的printf()。可能比用boost::format要好,但要考虑:
const string pi = "pi";
const char* m = "The value of %s is about %g (unless you live in %s).\n";
printf(m,  pi, 3.14159,  "Indiana");

printf()最简单的情形就是除了仅仅有格式字符窜外没有参数,我们首先这样处理:
void printf(const char* s)   
{
while (s) {
if (*s=='%' && *++s!='%')    // 确认没有更多参数
    // %%表示格式字符窜中普通的%
throw runtime_error("invalid format: missing arguments");
    std::cout << *s++;
}
}

可以了,我们必须处理printf()有更多参数的情形:
template<typename T, typename... Args>        // 注意"..."
void printf(const char* s, T value, Args... args)    // 注意"..."
{
while (s) {
if (*s=='%' && *++s!='%') {    // 格式标识符(不用理会是哪个)
std::cout << value;    // 使用第一个非格式参数
return printf(++s, args...);     // "剥掉"第一个参数
}
std::cout << *s++;
}
throw std::runtime error("extra arguments provided to printf");
}

该代码仅仅”剥掉”第一个非格式参数,接下来递归调用自己。当没有非非格式参数时,调用第一个(比较简单的)printf()(上面)。在编译时,这是一个非常标准的函数式编程实现。注意在格式化标识符中怎么重载<<取代使用(可能容易出错) “暗示”。
Args...定义了一个称谓”参数包”的东西。它是一个基本的序列(类型/值)对,你可以从第一个序列(类型/值)对开始”剥掉”参数。当用一个参数调用printf()时,选择第一个(printf(const char*))。当用两个或更多参数调用printf()时 ,选择第二个定义(printf(const char*, T value, Args... args)),第一个参数是s,第二个是value,其余的(如果有的话)捆绑到了参数包args里后来使用。在调用
printf(++s, args...);
过程中,参数包args被展开,以便下一个参数被当做value选中。这样继续下去,直到args为空(调用第一个printf())。
如果你熟悉函数式编程,对于一个很标准的技术有点不同反想。如果不是这样,这里有几个小的技术性例子可能有用。首先,我们可以声明并使用一个简单的变参模板函数(就像上面的printf()):
template<class ... Types>
void f(Types ... args);    // 变参模板函数
                // (也就是说,一个函数具有任意个任意类型的参数)
f();         // OK: args没有参数
f(1);         // OK: args含有一个参数: int
f(2, 1.0);     // OK: args含有两个参数: int和double
We can build a variadic type:

template<typename Head, typename... Tail>
class tuple<Head, Tail...>
: private tuple<Tail...> {    // 这是递归
// 一般而言,一个tuple存储它的头部(第一个 (type/value)对,接下来是tuple的尾
// (其余的(type/value)对),注意类型在类型中编码,不存放在数据中
typedef tuple<Tail...> inherited;
public:
tuple() { }    // 缺省: 空tuple

// 用分开的参数构造tuple:
tuple(typename add_const_reference<Head>::type v, typename
add_const_reference<Tail>::type... vtail)
    : m head(v), inherited(vtail...) { }

// Construct tuple from another tuple:
template<typename... VValues>
tuple(const tuple<VValues...>& other)
: m head(other.head()), inherited(other.tail()) { }

template<typename... VValues>
tuple& operator=(const tuple<VValues...>& other)    // 赋值
{
m head = other.head();
tail() = other.tail();
return *this;
}

typename add_reference<Head>::type head() { return m head; }
typename add_reference<const Head>::type head() const { return m head; }

inherited& tail() { return *this; }
const inherited& tail() const { return *this; }
protected:
Head m head;
}
给出了这个定义,我们可以加工tuple(拷贝和操作它们):
tuple<string,vector,double> tt("hello",{1,2,3,4},1.2);
string h = tt.head();    // "hello"
tuple<vector<int>,double> t2 = tt.tail();    // {{1,2,3,4},1.2};
涉及所有类型会有点繁琐,通常我们从参数类型来推断它们,例如,使用标准库make_tuple():
template<class... Types>
tuple<Types...> make_tuple(Types&&... t)    // 该定义有点简化(见标准20.5.2.2)
{
return tuple<Types...>(t);
}

string s = "Hello";
vector<int> v = {1,22,3,4,5};
auto x = make_tuple(s,v,1.2);
再看看
。标准14.6.3 变参模板
。[N2151==07-0011] D.?Gregor, J.?J?rvi: Variadic Templates for the C++0x Standard Library.
。[N2080==06-0150] D.?Gregor, J.?J?rvi, G.?Powell: Variadic Templates (Revision 3).
。[N2087==06-0157] Douglas Gregor: A Brief Introduction to Variadic Templates.
。[N2772==08-0282] L.?Joly, R.?Klarer: Variadic functions: Variadic templates or initializer lists? -- Revision 1.
。[N2551==08-0061] Sylvain Pion: A variadic std::min(T, ...) for the C++ Standard Library (Revision 2) .
________________________________________
一致的初始化语法和语义
依赖于一个对象的类型和初始化上下文,C++提供了几种初始化方法。当误用时,错误很奇怪,并且出错信息很酶涩。考虑:
string a[] = { "foo", " bar" };      // ok: 初始化数组变量
vector<string> v = { "foo", " bar" };// error: 初始化器列表用于非聚集vector
void f(string a[]);
f( { "foo", " bar" } );              // 语法错误: 块用作参数

int a = 2;              // “赋值风格”
int[] aa = { 2, 3 };    // 列表赋值风格
complex z(1,2);         // “函数风格”初始化
x = Ptr(y);             // “函数风格”用于转化/转换/构造

int a(1);        // 变量定义
int b();         // 函数声明
int b(foo);      // 变量定义或函数声明
很难记住这些初始化规则也很难选用最好的方法。
C++0x解决方案允许{}-初始化器列表用于所有的初始化:
X x1 = X{1,2};
X x2 = {1,2};    // = 非必须
X x3{1,2};
X* p = new X{1,2};
 
struct D : X {
D(int x, int y) :X{x,y} { /* ... */ };
};
 
struct S {
int a[3];
S(int x, int y, int z) :a{x,y,z} { /* ... */ }; // 老问题的解决方案
};
重要的是,X { a } 在每个上下文中构造了同样的值, 所以,在所有合法使用的地方,{ }初始化给出了同样的结果。例如:
X x{a};
X* p = new X{a};
z = X{a};         // 用作转换
f({a});           // 函数参数(类型X)
return {a};       // 函数返回值(函数返回X)
再看看
。C++草案部分 ???
。[N2215==07-0075] Bjarne Stroustrup and Gabriel Dos Reis: Initializer lists (Rev.3)
。[N2640==08-0150] Jason Merrill and Daveed Vandevoorde: Initializer Lists -- Alternative Mechanism and Rationale (v. 2) (final proposal)
________________________________________
右值引用
左值(可以用于赋值运算符的左侧)和右值(可以用于赋值运算符的右侧)的区分追溯到Christopher Strachey (C++祖先CPL 和指称语义之父)。在C++里,非const引用可以捆绑到左值上,const引用可以捆绑到左值或者右值上。目的在于避免人们在新值使用前去改变被销毁的临时值。例如:
void incr(int& a) { ++a; }
int i = 0;
incr(i);      // i变成了2
incr(0);      // error: 0不是左值
如果允许 incr(0) ,要么一些没有人能见到的临时值将会增长,要么更糟 – 0变成1。后者听起来有点蠢,但早期的Fortran编译器就有类似这样的bug,用了一个内存地址去存放0。
就到这里,不错,看一下:
template  swap(T& a, T& b)      // "古老风格的swap"
{
   T tmp(a);   // 现在我们有a的两个副本
   a = b;      // 现在我们有b的两个副本
   b = tmp;    // 现在我们有tmp (也是a)的两个副本
}
如果 T 是一个很高代价复制元素的类型,例如string和vector,swap就成了一个代价很高的操作(标准库中,我们有string和vector swap() 的特化版本处理这种情况)。注意到有些奇怪:我们根本就不想拷贝。我们仅仅希望移动a, b, 和 tmp 的值转一圈。
在C++0x中,我们可以定义”移动构造函数”和”移动赋值”用来移动而不是复制他们的参数值。
template class vector {
// ...
vector(const vector&);              // 拷贝构造函数
vector(vector&&);                  // 移动构造函数
vector& operator=(const vector&);   // 拷贝赋值
vector& operator=(vector&&); // 移动赋值
};  // 注意:移动构造函数和移动赋值使用非const&&
    // 它们可以,并通常这样做,写回它们的参数
&&指出是一个”右值引用”,一个右值引用可以捆绑到要么右值或左值上:
X a;
X f();
X& r1 = a;             // 把r1绑定到a(左值)
X& r2 = f();           // error: f()是右值;不能绑定
 
X&& rr1 = f(); // 不错: 把rr1绑定到临时值上
X&& rr2 = a;   // 也不错: 把rr2绑定到a(左值)
移动赋值的背后不是复制,它仅仅代表它的根源,使用廉价的缺省去取代,而不是拷贝。例如,对于字符窜 s1=s2 使用移动赋值不会拷贝 s2 的字符;相反,它仅仅让 s1 把这些字符当作自己的,从某种程度上删除 s1 原来的字符(可以把他们留在 s2 中 ,大概马上销毁)。
我们怎么知道从根源上仅仅移动是否可以呢?我们告诉编译器:
template 
void swap(T& a, T& b)  // “非常好的交换”(几乎)
{
T tmp = move(a);     // 导致a非法
a = move(b);         // 导致b非法
b = move(tmp);       // 导致tmp非法
}
move(x) 的意思是”你可以把 x 作为一个右值”。如果把 move() 称谓 rval() 更好,但是到目前为止 move() 已经用了几年了。 move() 模版函数 在C++0x可以写(见”简要介绍”) 并使用右值引用。
原来的 void swap(T& a, T& b) 仍然需要左值。现在,我们可以做得更好,这样C++0x提供:
template  void swap(T&& a, T& b);
template  void swap(T& a, T&& b);
template  void swap(T& a, T& b);
给出以上这些,我们就可以把左值和右值进行交换了。
vector v = {1, 2, 3, 4, 5, 6 }
swap(v,{});    // 清空v
               // v原来的元素被隐式的删除了
右值引用也可以用于提供非常好的钱向声明。
在C++0x标准库里面,所有的容器提供移动构造函数和移动赋值,以及插入新元素的操作,例如 insert() 和 push_back() 具有右值引用的版本。结果标准容器和算法安静的 - 无需用户干预 - 提升性能,因为他们复制的少了。
特别注意:关于右值引用是否应该能绑定到左值上是一个公开的议题。上面move()和swap()的使用将不会被这样的变更所影响,但是一些库设施的实现和重载集会受其影响。(见N2812=08-0322).
See also
。C++草案部分 ???
。N1385 N1690 N1770 N1855 N1952
。[N2027==06-0097] Howard Hinnant, Bjarne Stroustrup, and Bronek Kozicki: A brief introduction to rvalue references
。[N1377=02-0035] Howard E. Hinnant, Peter Dimov, and Dave Abrahams: A Proposal to Add Move Semantics Support to the C++ Language (original proposal).
。[N2118=06-0188] Howard Hinnant: A Proposal to Add an Rvalue Reference to the C++ Language Proposed Wording (Revision 3) (final proposal).
________________________________________
unions (广义的)
在C++98里(C++早些的版本也这样),一个具有用户定义了的构造函数,析构函数,赋值运算符成员变量不能成为union的成员:
union U {
int m1;
complex<double> m2;    // error(愚蠢): complex有构造函数
string m3;    // error(不愚蠢): string有变种
// 由构造函数,拷贝构造函数和析构函数来维护
};
特殊情况
U u;            // 如果有构造函数,那么用哪一个?
u.m1 = 1;        // 给int成员赋值
string s = u.m3;    // 灾难:从string成员读取
很明显,写入一个成员并读取另一个是非法的,但是人们有时这样做(通常错误行为)。
C++0x修改了关于union的这些限制条件,允许更灵活的成员类型;尤其需要指出,允许具有构造函数和析构函数的成员类型。并添加了一个限制条件,通过鼓励构造区别(discriminated)union让灵活的union不那么容易出错。
union成员类型有限制:
。没有虚函数(跟原来一样)
。没有引用(跟原来一样)
。没有基类(跟原来一样)
。如果一个union成员具有用户定义的构造函数,拷贝构造函数或析构汉函数,那么这个特殊的函数就是deleted;换句话说,不能用于union对象类型。这是新东西。
For example:
union U1 {
int m1;
complex<double> m2;    // ok
};
   
union U2 {
int m1;
string m3;    // ok
};
这看起来容易出错,但新约束条件有效。特别的:
U1 u;          // ok
u.m2 = {1,2};    // ok: 给complex成员赋值
U2 u2;        // error: 字符窜析构函数导致U析构函数无效
U2 u3 = u2;    // error: 字符窜的 = 导致U的 = 无效
一般而言,U2没有什么意义,除非你把它嵌入一个struct来追踪使用了哪个成员(变量)。所以构造区别union,例如:
class Widget {    // 三个备选实现表示一个union
private:
enum class Tag { point, number, text } type; // discriminant
union {        // representation
point p;      // point有构造函数
int i;
string s;     // string有缺省构造函数,拷贝构造函数和析构函数
};
// ...
widget& operator=(const widget& w) // 有必要,因为string变量
{
if (type==Tag::text && w.type==Tag::text) {
s = w.s;        // 通常的string赋值
return *this;
}

if (type==Tag::text) s.~string();    // 销毁(显式地!)

switch (type=w.type) {
case Tag::point: p = w.p; break;    // 普通拷贝
case Tag::number: i = w.i; break;
case Tag::text: new(&s)(w.s); break;    // 指定位置的new
}
return *this;
}
};
再看看
。C++草案部分9.5
。[N2544=08-0054] Alan Talbot, Lois Goldthwaite, Lawrence Crowl, and Jens Maurer: Unrestricted unions (Revison 2)
________________________________________
POD (广义的)
一个POD ("Plain Old Data") 是可以像C struct那样操作的东西,例如用memcpy()复制,用memset()初始化,等等。在C++98里,POD真正的定义依赖于一套约束条件,它们在使用语言特性定义struct时用到了:
struct S { int a; };    // S是POD
struct SS { int a; SS(int aa) : a(aa) { } }; // SS不是POD
struct SSS { virtual void f(); /* ... */ };
在C++0x里,S和SS都是"标准布局类型"(或者说POD),因为关于SS真的没有什么”魔法”:构造函数并不影响布局(用memcpy()没有问题),仅仅初始化规则问题(memset() 不能用 – 并不强化变种)。但是,SSS仍然有一个内嵌的vptr,不再像"plain old data"。 C++0x定义POD, trivially copyable types, trivial types, 和标准布局类型用于处理曾经是POD的不同的技术方面。POD是递归定义的
。如果所有成员和基类是POD,那么还是POD
。跟原来相同(详见9 [10])
。无虚函数
。无虚拟基类
。无引用
。无多重存取标识符
C++0x POD最重要的方面是添加或者删减构造函数并不影响布局或性能。
再看看:
。C++草案部分3.9和9[10]
。[N2294=07-0154] Beman Dawes: POD's Revisited; Resolving Core Issue 568 (Revision 4).
________________________________________
原始字符窜literals
在很多情况下,例如当你书写正则表达式用标准regex 库时,一个后斜杠(\)作为换行真会带来点麻烦 (因为正则表达式中后斜杠用来表示将出现特殊字符而代表不同字符种类)。考虑一下怎么写有双字用后斜杠分开的情况(\w\\w):
string s = "\\w\\\\w";    // 希望我没有写错
一般而言,"原始字符窜literal" 就是当反斜杠仅仅是反斜杠的字符窜literal,这样,我们的例子就成了:
string s = R"[\w\\w]";    // 无十分坚信这确实没问题
有关原始字符窜的建议这样表示的动人例子
"('(?:[^\\\\']|\\\\.)*'|\"(?:[^\\\\\"]|\\\\.)*\")|"    // 这5个后斜杠对吗?
// 即使专家也容易搞糊涂
R"[...]"比"普通的" "..."标示有点啰嗦,但是 当你没有后斜杠字符时"还有更多"也实属必要:怎么把引号放进原始字符窜里面?除非前面有],否则很容易:
R"["quoted string"]"    // 该字符窜是"引用字符窜"
那么,我们怎么把字符序列]"放进原始字符窜里面呢?幸好很少出现这种问题,不过"[...]"仅仅是缺省的定界符对罢了。在"[...]"里,我们可以把界定符加在[...]前后。例如
R"***["quoted string containing the usual terminator ("])"]***"
// 字符窜是"***["quoted string containing the usual terminator ("])"
]后的字符序列必须跟[前的字符序列完全相同。这样以来,我们(几乎)可以对付任意复杂的格式了。

。标准2.13.4
。[N2053=06-0123] Beman Dawes: Raw string literals. (original proposal)
。[N2442=07-0312] Lawrence Crowl and Beman Dawes: Raw and Unicode String Literals; Unified Proposal (Rev. 2). (final proposal combined with the User-defined literals proposal).
________________________________________
自定义literals
C++提供了一系列的literal内嵌类型(2.14 Literals):
123    // int
1s    // short int
1.2    // double
1.2F    // float
'a'    // char
1ULL    // unsigned long long
0xD0    // 16进制 unsigned
"as"    // string
但是,在C++98没有用户自定义类型的literals。 This can be a bother and aslo seen as a violation of the prinicple that user-defined types should be supported as well as built-in types are. In particular, people have requested:
"Hi!"s            // 字符窜,不是“0结尾的字符数组”
1.2i            // 虚数虚部
123.4567891234df    // 10进制浮点数(IBM)
101010111000101b    // 2进制
123.56km        // 不是英里!(单位)
1234567890123456789012345678901234567890x    // 扩展精度
C++0x支持”自定义litaral”,通过literal运算符的寓意把带有后缀的literal映射成一个期望的类型。例如:
constexpr complex operator "i"(long double d) // 虚literal
{
return {0,d};    // complex是literal类型
}

std::string operator "s"(const char* p, size_t n)    // std::string literal
{
return string(p,n);    // 需要自由存储分配空间
}
注意使用constexpr,以便能够编译时估值。这样,我们就可以这样写了
template void f(const T&);
f("Hello");    // 传递指针到char*
f("Hello"s);    // 传递(5个字符)字符窜对象
f("Hello\n"s);    // 传递(6个字符)字符窜对象

auto z = 2+1i;    // complex(2,1)
基本(实现)思想是后来解析可能的literal,编译器始终检查后缀。用户自定义literal机制简单允许用户指定一个新后缀以及应该怎么处理它前面的literal。不能重新定义内嵌literal的本意或扩充literal语法。literal运算符可以要求获取它(前面的)literal传递“cooked” (如果新后缀尚没有定义,它本来应该有的值)或”raw”(键入的字符窜字符序列)。
想得到一个”uncooked”字符窜,仅仅需要一个const char*参数就可以了:
Bignum operator"x"(const char* p)
{
return Bignum(p);
}

f(Bignum);
f(1234567890123456789012345678901234567890x);
这里C风格字符窜"1234567890123456789012345678901234567890"传递给了operator"x"()。注意,我没有必要显式的把这些数字放入字符窜,尽管我这样做了:
f("1234567890123456789012345678901234567890"x);
Literal运算符通过长度参数来区分cooked和uncooked:
string operator "s"(const char* p, size_t n);    // 计算长度
string operator "S"(const char* p);        // 保存换行等

"one\ttwo\n"s;    // cooked: 运算符"s"({'o', 'n', 'e', '\t', 't', 'w', 'o', '\n', 0} , 8);
"one\ttwo\n"S;    // uncooked: 运算符"S"({'o', 'n', 'e', '\', 't', 't', 'w', 'o', '\', 'n', 0});

后缀趋向于比较短(例如s 表示字符窜, i表示虚数, m表示米,x 表示扩展),这样以来不同的用法容易冲突。用namespaces阻止这种冲突:
namespace Numerics {
// ...
class Bignum { /* ... */ };
namespace literals {
    operator "X"(char const*);
}
}

using namespace Numerics::literals;
再看看
。标准2.14.8 用户定义literals
。[N2378==07-0238] Ian McIntosh, Michael Wong, Raymond Mak, Robert Klarer, Jens Mauer, Alisdair Meredith, Bjarne Stroustrup, David Vandevoorde: User-defined Literals (aka. Extensible Literals (revision 3)).
________________________________________
属性
“属性”是一种新标准语法,通过把可选的 和/或 厂商指定信息添加到源代码中,用来在杂乱的机制中提供次序。(例如 __attribute__, __declspec, 和 #pragma)。C++0x 属性 有别于现存的语法,它可以置于代码中任何必要的位置,而且始终跟前面直接相邻的语法实体关联。例如:
void f [[ noreturn ]] ()    // f()永远没有返回值
{
throw "error";     // OK
}

unsigned char c [[ align(double) ]] [sizeof(double)];
// 字符数组,为了跟double对齐

可以看到attribute可以置于双方括弧中[[ ... ]]。noreturn和align是标准中定义的四个属性中的两个;另外两个是:
struct B {
virtual void f [[ final ]] (); // 不要尝试覆盖
};

struct D : B {
void f();     // error
};

struct foo* f [[carries_dependency]] (int i); // 暗示优化器
int* g(int* x, int* y [[carries_dependency]]);
有人担忧会用属性 创建语言方言。建议用属性仅仅用于控制实体并不会影响程序本意而且还能帮助发现错误。(例如[[final]])或帮助优化器(例如 [[carries_dependency]])。
有人计划使用属性改善对OpenMP的支持。例如:
for [[omp::parallel()]] (int i=0; i<v.size(); ++i) {
// ...
}
可见,属性有些用处。
再看看
。标准:7.6.1 Attribute syntax and semantics, 7.6.2-5 Alignment, noreturn, final, carries_dependency, 8 Declarators, 9 Classes, 10 Derived classes, 12.3.2 Conversion functions
。[N2418=07-027] Jens Maurer, Michael Wong: Towards support for attributes in C++ (Revision 3)
________________________________________
Lambdas
lambda表达式是一种用于指定一个函数对象的行为的机制。lambda的主要作用是指定一个简单行为让某个函数去执行。如果该行为很普遍或者有点复杂,我们通常用一个函数对象或者函数来完成。但是,如果该行为很小且尤其它依赖于局部信息时,我们宁愿在调用函数时需要它的地方精确指定它。例如,考虑一下使用vector的值给一套indices排序的情形:
vector<int> indices = {3, 1, 2};
vector<int> v = {10, 20, 30};
std::sort(indices.begin(), indices.end(), [&](int a, int b) { return v[a]<v[b]; });
// 现在ndices应该为{ 1, 2, 3 }
参数[&](int a, int b) { return v[a]<v[b]; }是一个"lambda" (或 "lambda函数"或 "lambda表达式"),它指定一个操作,该操作把整形变量a和b 比较结果返回来;[&]中的&指定局部范围内使用该名字,而且用作”引用””&”(可选方案是用其"值"用"="也就是拷贝)。
在C++98里,我们本可以定义一个函数对象,把它的数据(有一个引用指向它)存起来,使用比较运算符:
template<typename Value, typename Data, typename Cmp>
struct indirect_cmp {
const Data& m;    // 引用该数据
Cmp cmp;
indirect_cmp(const Data& m, Cmp cmp) : m(m), cmp(cmp) {}
bool operator()(Value a, Value b) { return m[a] < m[b]; }
};

vector<int> indices = {3, 1, 2};
vector<int> v = {10, 20, 30};
std::sort(indices.begin(), indices.end(), indirect_cmp<int, vector<int>, less<int> >(v, less<int>()));
// 现在indices应该 { 1, 2, 3 }
对于一个小函数,例如这个非直接的比较,函数对象的的寓意有点繁琐,尽管生成的代码可能相同。在C++98里,这样的函数对象用于模板参数必须是非局部对象;在C++0x中 这样就没有必要了。
为了指定一个lambda你必须指定
。(可选)它的参数和类型(上面例子里中的(int a, int b))
。它的操作列表:使用的变量(包括参数),如果有的话,(上面例子中的[&])。如果不需要名字, lambda用[]开始。
。该行为当做一个块执行 (上面例子中的{ return v[a]<v[b]; })。
。(可选)返回类型使用新后缀返回类型语法;但一般而言,我们仅仅从返回语句推断返回类型。如果没有返回值,就推断成void。
再看看
。标准5.1.2 Lambda表达式
。[N1968=06-0038] Jeremiah Willcock, Jaakko Jarvi Doug Gregor, Bjarne Stroustrup Andrew Lumsdaine: Lambda expressions and closures for C++ (不同语法的原始建议)
。[N2550=08-0060] Jaakko Jarvi, John Freeman, Lawrence Crowl: Lambda Expressions and Closures: Wording for Monomorphic Lambdas (Revision 4) (最终建议).
________________________________________
局部类型作为模板参数
在C++98里,局部的和未指明的类型不能够作为模板参数。这本是一种负担,因此C++提升了该约束条件:
void f(vector<X>& v)
{
struct Less { bool operator<(const X& a, const X& b) { return a.v < b.v; }
};
sort(v.begin(), v.end(), Less());    // C++98: error: Less是局部对象
                    // C++0x: ok
}
在C++0x里,我们还可以使用lambda表达式作为可选方案:
void f(vector<X>& v)
{
sort(v.begin(), v.end(), [] (const X& a, const X& b) { return a.v < b.v; });
// C++0x
}
值得记住对于书写文档和激励好的设计而言,指明行为非常有用。而且,非局部(必须指明)实体可以复用。
C++0x also allows values of unnamed types to be used as template arguments:
template<typename T> void foo(T const& t){}
enum X { x };
enum { y };

int main()
{
foo(x);     // C++98: ok; C++0x: ok
foo(y);     // C++98: error; C++0x: ok
enum Z { z };
foo(z);    // C++98: error; C++0x: ok
}

再看看:
。Standard: Not yet: CWG issue 757
。[N2402=07-0262] Anthony Williams: Names, Linkage, and Templates (rev 2).
。[N2657] John Spicer: Local and Unnamed Types as Template Arguments.
________________________________________
C99特性
为了保持高度的一致性,和C标准委员会进行了合作,引进了语言上几个微小的变化:
。long long.
。Extended integral types (也就是可选的long int类型的规则)。
。UCN changes [N2170==07-0030] “lift the prohibitions on control and basic source universal character names within character and string literals.”
。concatenation of narrow/wide strings.
。Not VLAs (Variable Length Arrays; thank heaven for small mercies).
还加上了一些预处理扩展规则:
。__func__ a macro that expands to the name of the lexically current function
。__STDC_HOSTED__
。_Pragma: _Pragma( X ) expands to #pragma X
。vararg macros (overloading of macros with different number of arguments)
。#define report(test, ...) ((test)?puts(#test):printf(_ _VA_ARGS_ _))
。empty macro arguments
从C99继承了很多标准库机制(本来所有C99库的变化都来自它的前任C89):
看看:
。Standard: 16.3 Macro replacement.
[N1568=04-0008] P.J. Plauger: PROPOSED ADDITIONS TO TR-1 TO IMPROVE COMPATIBILITY WITH C99.
________________________________________
extended integer types
一个扩展(精度)整型类型应该怎样有一套规则。
看看:
。[06-0058==N1988] J. Stephen Adamczyk: Adding extended integer types to C++ (Revision 1).
________________________________________
Dynamic Initialization and Destruction with Concurrency
抱歉,我还没有时间写该项,请将来回访。

。最终建议
________________________________________
copying and rethrowing exceptions
你怎么捕获一个异常并在另一个线程中再次抛出呢?用一点库技巧,就如标准18.8.5 异常传递描述的那样:
。exception_ptr current_exception(); Returns: An exception_ptr object that refers to the currently handled exception (15.3) or a copy of the currently handled exception, or a null exception_ptr object if no exception is being handled. The referenced object shall remain valid at least as long as there is an exception_ptr object that refers to it. ...
。void rethrow_exception(exception_ptr p);
。template exception_ptr copy_exception(E e); Effects: as if
try {
    throw e;
} catch(...) {
    return current_exception();
}
________________________________________
Extern模板
模板特化可以显式的声明作为一种方法用来压缩多重实例化。例如:
#include "MyVector.h"

extern template class MyVector; // 下面压缩隐式的实例化 –
// MyVector将会在别处显式的实例化

void foo(MyVector& v)
{
// 该处使用vector
}

这个“elsewhere”可能看起来类似下面这样:
#include "MyVector.h"

template class MyVector; // 让MyVector对于用户可见 (例如,共享库)
这是一个避免编译器和连接器做明显重复工作的方法。
看看
。Standard 14.8.2 Explicit instantiation
[N1448==03-0031] Mat Marcus and Gabriel Dos Reis: Controling Implicit Template Instantiation.
________________________________________
Namespace Associations (Strong using)
抱歉,我还没有时间写该项,请将来回访。
________________________________________
显示转换运算符
C++98提供了隐式和显式构造函数;也即是说一个构造函数声明explicit时仅仅用于显式转换,其它构造函数也可以用于隐式转换。例如:
struct S { S(int); };    // "常规构造函数" 定义了隐式转换
S s1(1);    // ok
S s2 = 1;    // ok
void f(S);
f(1);        // ok (但常常会出现糟糕的出乎预料 – 如果S时vector会如何呢?)

struct E { explicit E(int); };    // 显式构造函数
E e1(1);    // ok
E e2 = 1;    // error (但常常出乎预料)
void f(E);
f(1);        // error (避免出乎预料 -- 例如std::vector的构造函数对于int类型参数是显式的)

不过,构造函数不是定义转换的唯一机制。如果我们无法改变一个类,我们可以在另一个类里定义一个转换运算符。例如:
struct S { S(int) { } /* ... */ };

struct SS {
int m;
SS(int x) :m(x) { }
operator S() { return S(m); }  // 因为S没有S(SS); 不会引起麻烦
};

SS ss(1);
S s1 = ss;    // ok; 类似隐式构造函数
S s2(ss);        // ok ; 类似隐式构造函数
void f(S);
f(ss);            // ok; 类似隐式构造函数
不幸的是,没有显式的转换运算符(因为有问题的例子几乎没有)。C++0x通过允许转换运算符可以未显式的来处理这种问题。例如:
struct S { S(int) { } };

struct SS {
int m;
SS(int x) :m(x) { }
explicit operator S() { return S(m); }  // 因为S没有S(SS)
};

SS ss(1);
S s1 = ss;    // error; 类似显式构造函数
S s2(ss);        // ok ; 类似显式构造函数
void f(S);
f(ss);            // error; 类似显式构造函数

再看看:
。Standard: 12.3 Conversions
[N2333=07-0193] Lois Goldthwaite, Michael Wong, and Jens Maurer: Explicit Conversion Operator (Revision 1).
________________________________________
unique_ptr
unique_ptr (在<memory>中定义) 提供了一个严格的所有权语义。
拥有它的指针指向的对象owns the object it holds a pointer to
非拷贝可构造, 非拷贝可赋值, 但是却移动可构造和移动可赋值。
存储了一个指向对象的指针,当它销毁的时候(例如离开作用域(6.7))使用相关联的删除器删除该对象。
unique_ptr的使用包括
对于动态分配的内存提供异常安全,
把动态分配内存所有权传递给函数,
从函数返回动态分配的内存。
在容器中存储指针
auto_ptr本应该怎样?
unique_ptr极度依赖 右值引用 和移动语义。
下面有一段常见的异常不安全代码:
X* f()
{
X* p = new X;
// 做一些事情,可能抛出异常
return p;
}
一种办法是把指向存放在自由存储空间上对象的指针存储在unique_ptr中:
X* f()
{
unique_ptr<X> p(new X);        // 或{new X}但不是 = new X
// 做一些事情,可能抛出异常
return p.release();
}
现在,如果抛出异常的话,unique_ptr将会(隐式的)销毁指向的对象。这是很基本的RAII。 然而,除非我们真的需要返回一个内嵌的指针,否则我们返回一个unique_ptr会更好:
unique_ptr<X> f()
{
unique_ptr<X> p(new X);        // 或{new X} 但不是 = new X
//做一些事情,可能抛出异常
return p;    // 所有权从f()中传递出来了
}
我们可以这样使用这个f:
void g()
{
unique_ptr<X> q = f();  // 使用移动构造函数移动
q->memfct(2);           // 使用q
X x = *q;               // 复制指向的对象
}    // q 和q拥有的对象退出时被销毁了
unique_ptr具有 "移动语义",用调用f()的右值初始化q就把所有权转给了q。
unique_ptr的用法之一是作为容器的指针,除非异常安全问题 (确保指向的元素能被销毁),在此我们可能使用一个内嵌的指针:
vector<unique_ptr<string>> vs = { new string{"Doug"}, new string{"Adams"} };
unique_ptr通过一个简单的内嵌指针来表示,跟内嵌指针相比,这点开销微乎其微。因为,unique_ptr并不提供任何形式的动态检查。
再看看
。C++草案部分20.7.10
。Howard E. Hinnant: unique_ptr Emulation for C++03 Compilers
。最终建议。
________________________________________
shared_ptr
TBD
________________________________________
weak_ptr
TBD
________________________________________
垃圾收集应用二进制接口
垃圾收集(自动回收没有引用的内存区域)在C++里面是可选的,垃圾收集器不是必须实现的一部分。但是, C++0x提供一个GC能做什么的定义,如果用垃圾收集的话,C++0x还提供一个ABI(Application Binary Interface)帮助控制它的行为。
有关指针和生存期的规定在"安全继承的指针"(3.7.4.3) [译者注:这里的”继承”跟OO中的术语”继承”大相径庭,下同]; 大致这样:"指针指向由new分配的对象或者它的子对象。" 这里有几个关于"不安全继承指针"的例子,也就是"掩盖了指针",即如果你希望编码优雅和易于理解,在程序中就不要这样做:
让一个指针指向"别处"一会
int* p = new int;
p+=10;
// ... 收集器可能会运行到此处 ...
p-=10;
*p = 10;    // 我们还能确定int仍然在原来位置吗?
把指针藏在一个int里
int* p = new int;
int x = reinterpret_cast<int>(p);    // 无法移植
p=0;
// ... 收集器可能会运行到此处 ...
p = reinterpret_cast<int*>(x);
*p = 10;    //我们还能确定int仍然在原来位置吗?
有更多甚至更糟糕的欺骗I/O操作手法,想想"把很多bit分散到不同的word里面", ... 有正当的理由掩盖指针(例如在内存极度受限的应用中采用xor欺骗手法),不过也不象有些程序员们想象的那么多糟糕的地方。
程序员可以指定没有指针的地方(例如在图片里)以及指定当收集器找不到指针时哪些内存不用回收:
void declare_reachable(void* p);    // 内存区由p开始
// (由某个分配器分配并记下了该内存块大小)
//  一定不要回收它
template<class T> T* undeclare_reachable(T* p);
void declare_no_pointers(char* p, size_t n);       // p[0..n]没有存放指针
void undeclare_no_pointers(char* p, size_t n);
程序员也可以询问哪个规则指针安全和强行回收:
enum class pointer_safety {relaxed, preferred, strict };
pointer_safety get_pointer_safety();
3.7.4.3[4]: 如果一个指针值不是安全继承的指针值,解除引用或者释放了,而且引用的对象在动态存储阶段原来并未声明可达(20.7.13.7),该行为则没有定义。
。relaxed: 安全继承和非安全继承的指针同等对待;像C和C++98那样,但这并非我想要的- 我希望如果一个对象没有合法的指针指向它就允许GC回收。
。preferred: 类似于relaxed;但一个垃圾收集器可能当做内存泄露检查器 和/或 引用"野指针"的检查器。
。strict: 安全继承和非安全继承的指针分开对待,也就是说垃圾收集器可能不理会非安全继承的指针。
没有一个标准的方案说你更喜欢哪一个。考虑一下"实现质量" 和"编程环境"问题吧。
再看看
。C++草案3.7.4.3
。C++草案20.7.13.7
。Hans Boehm's GC page
。Hans Boehm's Discussion of Conservative GC
。最终建议。
________________________________________
尚没有写
抱歉,我还没有时间写该项,请将来回访。
 

posted on 2009-01-11 21:58 Chipset 阅读(2273) 评论(6)  编辑 收藏 引用 所属分类: 文章翻译

Feedback

# re: C++ 0x FAQ (翻译版, 更新中...) 2009-06-22 13:10 asdfasdfasdf

很辛苦~ 支持你~ 我很关注C++0x  回复  更多评论   

# re: C++ 0x FAQ (翻译版, 更新中...) 2009-07-11 14:45 sinojelly

支持,加油!!  回复  更多评论   

# re: C++ 0x FAQ (翻译版, 更新中...) 2009-08-08 12:01 ldc

concept已經移除了。  回复  更多评论   

# re: C++ 0x FAQ (翻译版, 更新中...) 2009-08-08 12:02 ldc

希望樓主繼續更新啊  回复  更多评论   

# re: C++ 0x FAQ (翻译版, 更新中...)[未登录] 2009-10-11 09:22 chipset

这些日子忙的一踏糊涂,等有时间了我会一次性全部更新,大概要等到Bjarne Stroustrup几乎回答了所有问题后。  回复  更多评论   

# re: C++ 0x FAQ (翻译版, 更新中...) 2010-01-10 10:53 bupteinstein

谢谢!关注中!  回复  更多评论   


专题:iPad jQuery Chrome OS

博客园首页  IT新闻  闪存  学英语
标题  
姓名  
主页
验证码 *
内容(提交失败后,可以通过“恢复上次提交”恢复刚刚提交的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
[使用Ctrl+Enter键可以直接提交]
每天10分钟,轻松学英语
网站导航: