[转]C++强大背后

2014-01-22 分类:互联网 阅读(9295) 评论(6)

在31年前(1979年),一名刚获得博士学位的研究员,为了开发一个软件项目发明了一门新编程语言,该研究员名为Bjarne Stroustrup,该门语言则命名为——C with classes,四年后改称为C++C++是一门通用编程语言,支持多种编程范式,包括过程式、面向对象(object-oriented programming, OP)、泛型(generic programming, GP),后来为泛型而设计的模版,被发现证明是图灵完备的,因此使C++亦可支持模版元编程范式(template metaprogramming, TMP)。C++继承了C的特色,既为高级语言,又含低级语言功能,可同时作为系统和应用编程语言。

C++广泛应用在不同领域,使用者以数百万计。根据近十年的调查,C++的流行程度约稳定排行第3位(于C/Java之后)。 C++经历长期的实践和演化,才成为今日的样貌。1998年,C++标准委员会排除万难,使C++成为ISO标准(俗称C++98),当中含非常强大的标准模版库(standard template library, STL)。之后委员会在2005年提交了有关标准库的第一个技术报告(简称TR1),并为下一个标准C++0x而努力。可惜C++0x并不能在200x年完成,各界希望新标准能于2011年内出台。

流行的C++编译器中,微软Visual C++ 2010已实现部分C++0x语法并加入TR1扩充库,而gcc对C++0x语法和库的支持比VC2010更多。

应否选择C++

哪些程序适宜使用C++?

C++并非万能丹,我按经验举出一些C++的适用时机。

  • C++适合构造程序中需求较稳定的部分,需求变化较大的部分可使用脚本语言;
  • 程序须尽量发挥硬件的最高性能,且性能瓶颈在于CPU和内存;
  • 程序须频繁地与操作系统或硬件沟通;
  • 程序必须使用C++框架/库,如大部分游戏引擎(如Unreal/Source)及中间件(如Havok/FMOD),虽然有些C++库提供其他语言的绑定,但通常原生的API性能最好、最新;
  • 项目中某个目标平台只提供C++编译器的支持。

按应用领域来说,C++适用于开发服务器软件、桌面应用、游戏、实时系统、高性能计算、嵌入式系统等。

使用C++还是C?

C++和C的设计哲学并不一样,两者取舍不同,所以不同的程序员和软件项目会有不同选择,难以一概而论。与C++相比,C具备编译速度快、容易学习、显式描述程序细节、较少更新标准(后两者也可同时视为缺点)等优点。在语言层面上,C++包含绝大部分C语言的功能(例外之一,C++没有C99的变长数组VLA),且提供OOP和GP的特性。但其实用C也可实现OOP思想,亦可利用宏去实现某程度的GP,只不过C++的语法能较简洁、自动地实现OOP/GP。C++的RAII(resource acquisition is initialization,资源获取就是初始化)特性比较独特,C/C#/Java没有相应功能。回顾历史,Stroustrup开发的早期C++编译器Cpre/Cfront是把C++源代码翻译为C,再用C编译器编译的。由此可知,C++编写的程序,都能用等效的C程序代替,但C++在语言层面上提供了OOP/GP语法、更严格的类型检查系统、大量额外的语言特性(如异常、RTTI等),并且C++标准库也较丰富。有时候C++的语法可使程序更简洁,如运算符重载、隐式转换。但另一方面,C语言的API通常比C++简洁,能较容易供其他语言程序调用。因此,一些C++库会提供C的API封装,同时也可供C程序调用。相反,有时候也会把C的API封装成C++形式,以支持RAII和其他C++库整合等。

为何C++性能可优于其他语言?

相对运行于虚拟机语言(如C#/Java),C/C++直接以静态形式把源程序编译为目标平台的机器码。一般而言,C/C++程序在编译及链接时可进行的优化最丰富,启动时的速度最快,运行时的额外内存开销最少。而C/C++相对动态语言(如Python/Lua)也减少了运行时的动态类型检测。此外,C/C++的运行行为是确定的,且不会有额外行为(例如C#/Java必然会初始化变量),也不会有如垃圾收集(GC)而造成的不确定性延迟,而且C/C++的数据结构在内存中的布局也是确定的。有时C++的一些功能会使程序性能优于C,当中以内联和模版最为突出,这两项功能使C++标准库的sort()通常比C标准库的qsort()快多倍(C可用宏或人手编码去解决此问题)。另一方面,C/C++能直接映射机器码,之间没有另一层中间语言,因此可以做底层优化,例如使用内部(intrinsic)函数和嵌入汇编语言。然而,许多C++的性能优点并非免费午餐,代价包括较长的编译链接时间和较易出错,因而增加开发时间和成本,这点稍后补充。

我进行了一个简单全局渲染性能测试(512×512像素,每像素10000个采样),C++ 1小时36分、Java 3小时18分、Python约18天、Ruby约351天。评测方式和其他语言的结果详见博文

C++常见问题

C++源代码跨平台吗?

C++有不错的跨平台能力,但由于直接映射硬件,因性能优化的关系,跨平台能力不及Java及多数脚本语言。然而,实践跨平台的C++软件还是可行的,但须注意以下问题:

  • C++标准没有规定原始数据类型(如int)的大小,需要特定大小的类型时,可自订类型(如int32_t),同时对任何类型使用sizeof()而不假设其大小;
  • 字节序(byte order)按CPU有所不同,特别要注意二进制输入输出、reinterpret_cast法;
  • 原始数据和结构类型的地址对齐有差异;
  • 编译器提供的一些编译器或平台专用扩充指令;
  • 避免作应用二进制接口(application binary interface, ABI)的假设,例如调用函数时参数的取值顺序在C/C++中没定义,在C++中也不可随便假设RTTI/虚表等实现方式。

总括而言,跨平台C++软件可在头文件中用宏检测编译器和平台,再用宏、typedef、自定平台相关实现等方法去实践跨平台,C++标准不会提供这类帮助。

C++程序容易崩溃?

和许多语言相比,C/C++提供不安全的功能以最优化性能,有可能造成崩溃。但要注意,很多运行时错误,如向空指针/引用解引用、数组越界、堆栈溢出等,其他语言也会报错或抛出异常,这些都是程序问题,而不是语言本身的问题。有些意见认为,出现这类运行时错误,应该尽量写入日志并立即崩溃,不该让程序继续运行,以免造成更大的影响(例如程序继续把内存中错误的数据覆写文件)。若要容错,可按业务把程序分割为多进程,像Chrome或使用fork()的形式。然而,C++有许多机制可以减少错误,例如以string代替C字符串;以vectorarray(TR1)代替原始数组(有些实现可在调试模式检测越界);使用智能指针也能减少一些原始指针的问题。另外,我最常遇到的Bug,就是没有初始化成员变量,有时会导致崩溃,而且调试版和发行版的行为可能不同。

C++要手动做内存管理?

C++同时提供在堆栈上的自动局部变量,以及从自由存储(free store)分配的对象。对于后者,程序员需手动释放,或使用不同的容器和智能指针。 C++程序员经常进一步优化内存,自定义内存分配策略以提升效能,例如使用对象池、自定义的单向/双向堆栈区等。虽然C++0x还没加入GC功能,但也可以自行编写或使用现成库。此外,C/C++也可以直接使用操作系统提供的内存相关功能,例如内存映射文件、共享内存等。

使用C++常要重造轮子?

我曾参与的C++项目,都会重造不少标准库已提供的功能,此情况在其他语言中较少出现。我试图分析个中原因。首先,C++标准库相对很多语言来说是贫乏的,各开发者便会重复地制造自订库。从另一个角度看,C++标准库是用C++编写的(很多其他语言不用自身而是用C/C++去编写库),在能力和性能上,自订库和标准库并无本质差别;另外,标准库为通用而设,对不同平台及多种使用需求作取舍,性能上有所影响,例如EA公司就曾发表自制的EASTL规格,描述游戏开发方面对STL的性能及功能需求的特点;此外,多个C++库一起使用,经常会因规范不同而引起冲突,又或功能重叠,所以项目可能须自行开发,或引入其他库的概念或实现(如Boost/TR1/Loki),改写以符合项目规范。

C++编译速度很慢?

错,是非常慢。我认为C++可能是实用程序语言中编译速度最慢的。此问题涉及C++沿用C的编译链接方式,又加入了复杂的类/泛型声明和内联机制,使编译时间倍增。在C++对编译方法改革之前(如module提案),可使用以下技巧改善:第一,使用pimpl手法,因性能损耗应用于调用次数不多的类;第二,仅包含必要头文件,并尽量使用及提供前置声明版本的头文件(如iosfwd);第三采用基于接口的设计,但须注意虚函数调用成本;第四,采用unity build,即把多个cpp文件结合在一个编译单元进行编译;第五,采用分布式生成系统如IncrediBuild

C++缺乏什么功能?

虽然C++已经非常复杂,但仍缺少很多常见功能。 C++0x作出了不少改善,例如语言方面加入Lambda函数、闭包、类型推导声明等,而库方面则加入正则表达式、采用哈希表的unordered_set/unordered_map、引用计数智能指针shared_ptr/weak_ptr等。但最值得留意的是C++0x引入多线程的语法和库功能,这是C++演进的一大步。然而,模组、GC、反射机制等功能虽有提案,却未加进C++0x。

C++使用建议

为应用挑选特性集

我同意Stroustrup关于使用C++各种技术的回应:“你可以做,不意味着你必须这么做。(Just because you can do it, doesn’t mean that you have to.)” C++充满丰富的特性,但同时带来不同问题,例如过分复杂、编译及运行性能的损耗。一般可考虑是否使用多重继承、异常、RTTI,并调节使用模版及模版元编程的程度。使用过分复杂的设计和功能,可能会令部分团队成员更难理解和维护。

为团队建立编程规范

C++的编码自由度很高,容易编写风格迥异的代码,C++本身也没有定义一些标准规范。而且,C++的源文件物理构成,较许多语言复杂。因此,除了决定特性集,每个团队应建立一套编程规范,包括源文件格式(可使用文件模版)、花括号风格。

尽量使用C++风格而非C风格

由于C++有对C兼容的包袱,一些功能可以使用C风格实现,但最好使用C++提供的新功能。最基本的是尽量以具名常量、内联函数和泛型取代宏,只把宏用在条件式编译及特殊情况。旧式的C要求局部变量声明在作用域开端,C++则无此限制,应把变量声明尽量置于邻近其使用的地方,for()的循环变量声明可置于for的括号内。 C++中能加强类型安全的功能应尽量使用,例如避免“万能”指针void *,而使用个别或泛型类型;用bool而非int表示布尔值;选用4种C++ cast关键字代替简单的强制转换。

结合其他语言

如前文所述,C++并非适合所有应用情境,有时可以混合其他语言使用,包括用C++扩展其他语言,或在C++程序中嵌入脚本语言引擎。对于后者,除了使用各种脚本语言的专门API,还可使用BoostSWIG作整合。

C++学习建议

C++缺点之一,是相对许多语言复杂,而且难学难精。许多人说学习C语言只需一本K&R《C程序设计语言》即可,但C++书籍却是多不胜数。我是从C进入C++,皆是靠阅读自学。在此分享一点学习心得。个人认为,学习C++可分为4个层次:

由于我主要是应用C++,大约只停留于第二、三个层次。然而,C++只是软件开发的一环而已,单凭语言并不能应付业务和工程上的问题。建议读者不要强求几年内“彻底学会C++的知识”,到达第二层左右便从工作实战中汲取经验,有兴趣才慢慢继续学习更高层次的知识。虽然学习C++有难度,但也是相当有趣且有满足感的。

数十年来,C++虽有起伏,但她依靠其使用者而不断得到顽强的生命力,相信在我退休之前都不会与她分离,也希望更进一步了解她,与她走进未来。


本文原于《程序员》2010年8月刊揭载。

最新文章

  1. Logstash之multiline 插件
  2. magic_quotes_gpc和magic_quotes_runtime的区别和用法详解
  3. uva12546. LCM Pair Sum
  4. [python 译] 基于面向对象的分析和设计
  5. CSS学习总结(一)
  6. Ruby中Block, Proc, 和Lambda
  7. 使用Go开发web服务器
  8. H5小游戏的坑点小结
  9. Future 模式介绍
  10. URAL 1227 Rally Championship(树的直径)(无向图判环)
  11. WCF再学习小结
  12. git命令行
  13. Aizu 2305 Beautiful Currency DP
  14. ArcGIS Runtime for Android开发教程V2.0(8)基础篇-----地图事件
  15. CSS样式学习笔记『W3School』
  16. COJ 0990 WZJ的数据结构(负十)
  17. ifstream中文路径问题分析
  18. X11 五子棋
  19. h5 录音
  20. Binding介绍

热门文章

  1. 三列布局中 float引发的一个问题-当“非float的元素”和“float的元素”在一起的时候,如果非float元素在先,那么float的元素将受到排斥。
  2. [Ynoi2015]盼君勿忘
  3. [WordPress]基本操作
  4. Java中最小的整数为什么是-2147483648
  5. 恩布企业IM PC端,服务端公布 1.16 版本号
  6. 利用runtime动态生成对象?
  7. java,wavToMP3格式转换
  8. 数据库技术_Orcale技术(0002)_5分钟会用存储过程_存储过程实例
  9. luogu3111 [USACO14DEC]牛慢跑Cow Jog_Sliver
  10. svn代码回滚命令【转】