C++的效率浅析

自从七十年代C语言诞生以来,一直以其灵活性、高效率和可移植性为软件开发人员所钟爱,成为系统软件开发的首选工具。而C++作为C语言的继承和发展,不仅保留了C语言的高度灵活、高效率和易于理解等诸多优点,还包含了几乎所有面向对象的特征,成为新一代软件系统构建的利器。 

  相对来说,C语言是一种简洁的语言,所涉及的概念和元素比较少,主要是:宏(macro)、指针(pointer)、结构(struct)、函数(function)和数组(array),比较容易掌握和理解。而C++不仅包含了上面所提到的元素,还提供了私有成员(private members)、公有成员(public members)、函数重载(function overloading)、缺省参数(default parameters)、构造函数、析构函数、对象的引用(references)、操作符重载(operator overloading)、友元(friends)、模板(templates)、异常处理(exceptions)等诸多的要素,给程序员提供了更大的设计空间,同时也增加了软件设计的难度。

  C语言之所以能被广泛的应用,其高效率是一个不可忽略的原因,C语言的效率能达到汇编语言的80%以上,对于一种高级语言来说,C语言的高效率就不言而喻了。那么,C++相对于C来说,其效率如何呢?实际上,C++的设计者stroustrup要求C++效率必须至少维持在与C相差5%以内,所以,经过精心设计和实现的C++同样有很高的效率,但并非所有C++程序具有当然的高效率,由于C++的特殊性,一些不好的设计和实现习惯依然会对系统的效率造成较大的影响。同时,也由于有一部分程序员对C++的一些底层实现机制不够了解,就不能从原理上理解如何提高软件系统的效率。

  本文主要讨论两个方面的问题:第一,对比C++的函数调用和C函数调用,解析C++的函数调用机制;第二,例举一些C++程序员不太注意的技术细节,解释如何提高C++的效率。为方便起见,本文的讨论以下面所描述的单一继承为例(多重继承有其特殊性,另作讨论)。

										

BR>
  C++的的函数分为四种:内联函数(inline member function)、静态成员函数(static member function)、虚函数(virtual member function)和普通成员函数。

  内联函数类似于C语言中的宏定义函数调用,C++编译器将内联函数的函数体扩展在函数调用的位置,使内联函数看起来象函数,却不需要承受函数调用的开销,对于一些函数体比较简单的内联函数来说,可以大大提高内联函数的调用效率。但内联函数并非没有代价,如果内联函数体比较大,内联函数的扩展将大大增加目标文件和可运行文件的大小;另外,inline关键字对编译器只是一种提示,并非一个强制指令,也就是说,编译器可能会忽略某些inline关键字,如果被忽略,内联函数将被当作普通的函数调用,编译器一般会忽略一些复杂的内联函数,如函数体中有复杂语句,包括循环语句、递归调用等。所以,内联函数的函数体定义要简单,否则在效率上会得不偿失。

静态函数的调用,如下面的几种方式: 


BR>
  将被编译器转化为一般的C函数调用形式,如同这样: 


BR>
  mangled_name_of_X_StaticFunc()是指编译器将X::StaticFunc()函数经过变形(mangled)后的内部名称(C++编译器保证每个函数将被mangled为独一无二的名称,不同的编译器有不同的算法,C++标准并没有规定统一的算法,所以mangled之后的名称也可能不同)。可以看出,静态函数的调用同普通的C函数调用有完全相同的效率,并没有额外的开销。

普通成员函数的调用,如下列方式: 


BR>
  将被被编译器转化为如下的C函数调用形式,如同这样。 


BR>
  可以看出普通成员函数的调用同普通的C调用没有大的区别,效率与静态函数也相同。编译器将重新改写函数的定义,增加一个const X* this参数将调用对象的地址传送进函数。

  虚函数的调用稍微复杂一些,为了支持多态性,实现运行时刻绑定,编译器需要在每个对象上增加一个字段也就是vptr以指向类的虚函数表vtbl,如类X的对象模型如下图所示(本文中对此不多做解释,若想进一步了解,可以参考其它材料)。

虚函数的多态性只能通过对象指针或对象的引用调用来实现,如下的调用: 


BR>
  将被C++编译器转换为如下的形式。 


BR>
  其中的2表示VirtualFunc在类虚函数表的第2个槽位。可以看出,虚函数的调用相当于一个C的函数指针调用,其效率也并未降低。

  由以上的四个例子可以看出,C++的函数调用效率依然很高。但C++还是有其特殊性,为了保证面向对象语义的正确性,C++编译器会在程序员所编写的程序基础上,做大量的扩展,如果程序员不了解编译器背后所做的这些工作,就可能写出效率不高的程序。对于一些继承层次很深的派生类或在成员变量中包含了很多其它类对象(如XX中的m_strName变量)的类来说,对象的创建和销毁的开销是相当大的,比如XX类的缺省构造函数,即使程序员没有定义任何语句,编译器依然会给其构造函数扩充以下代码来保证对象语义的正确性:


BR>
  所以为了提高效率,减少不必要的临时对象的产生、拖延暂时不必要的对象定义、用初始化代替赋值、使用构造函数初始化列表代替在构造函数中赋值等方法都能有效提高程序的运行效率。以下举例说明: 

1、 减少临时对象的生成。如以传送对象引用的方式代替传值方式来定义函数的参数,如下例所示,传值方式将导致一个XX临时对象的产生 


BR>
  2、 拖延暂时不必要的对象定义。在C中要将所有的局部变量定义在函数体头部,考虑到C++中对象创建的开销,这不是一个好习惯。如下例,如果大部分情况下bCache为"真",则拖延xx的定义可以大大提高函数的效率。 


BR>
  3、 可能情况下,以初始化代替先定义后赋值。如下例,高效率的做法会比效率不高的做法省去了cache变量的缺省构造函数调用开销。 


BR>
  4、 在构造函数中使用成员变量的初始化列表代替在构造函数中赋值。如下例,在效率不高的做法中,XX的构造函数会首先调用m_strName的缺省构造函数,再产生一个临时的String object,用空串""初始化临时对象,再以临时对象赋值(assign)给m_strName,然后销毁临时对象。而高效的做法只需要调用一次m_strName的构造函数。


BR>
  类似的例子还很多,如何写出高效的C++程序需要实践和积累,但理解C++的底层运行机制是一个不可缺少的步骤,只要平时多学习和思考,编写高效的C++程序是完全可行的。

时间: 2024-09-29 15:54:24

C++的效率浅析的相关文章

浅析PHP中的i++与++i的区别及效率_php实例

先看看基本区别: i++ :先在i所在的表达式中使用i的当前值,后让i加1 ++i :让i先加1,然后在i所在的表达式中使用i的新值 看一些视频教程里面写for循环的时候都是写 ++i 而不是 i++,上网搜索了一下,原来有效率问题 ++i相当于下列代码 i += 1; return i; i++相当于下列代码 j = i; i += 1; return j; 当然如果编译器会将这些差别都优化掉,那么效率就都差不多了. 再给大家详细说下++i 与 i++ 的区别 1.++i 的用法(以 a=++

Oracle中like效率正则表达式浅析

Oracle 中like常用但是其效率不是高.   特别是使用%a%----->全局扫描,没有利用到任何索引.   情况可以的条件尽量下使用a%------>可以利用正序的索引.                           %a------>可以利用反序的索引(当然得已有反序的索引). 使用instr函数取代like查询,可提高效率,在海量数据中效果尤其明显. 1.%a%方式: select * from pub_yh_bm t where instr(t.chr_bmdm,'2

浅析PHP的静态成员函数效率更高的原因_php技巧

很多php开发人员都知道, 使用类的静态成员函数效率比类的普通成员函数的要高,本文从应用层次分析这个问题 下面是一个范例: 复制代码 代码如下: <?php // php静态方法测试header('Content-Type: text/html; charset=utf-8');class xclass{     public static $var1 = '1111111111111111';     public $var2 = 'aaaaaaaaaaaaa';     public fun

浅析PHP中的i++与++i的区别及效率

先看看基本区别: i++ :先在i所在的表达式中使用i的当前值,后让i加1 ++i :让i先加1,然后在i所在的表达式中使用i的新值 看一些视频教程里面写for循环的时候都是写 ++i 而不是 i++,上网搜索了一下,原来有效率问题 ++i相当于下列代码 i += 1; return i; i++相当于下列代码 j = i; i += 1; return j; 当然如果编译器会将这些差别都优化掉,那么效率就都差不多了. 再给大家详细说下++i 与 i++ 的区别 1.++i 的用法(以 a=++

浅析如何提高SEO优化团队效率

中介交易 SEO诊断 淘宝客 云主机 技术大厅 自2008年以来SEO这个词逐渐进入我们的视野,在经过多年的发展以及行业的发展涌现了许多优秀的SEO团队以及SEO优化公司,整个行业进行了新一轮的发展势头,但是也面临着对应的难题,也就是SEO优化团队效率的把控. (领导:小子,抓紧时间发外链!) 如何进行分工合作 一个SEO团队的主要组成由以下几个小组组成,每一个小组负责各自的核心工作,这是我常见的SEO团队组成结构,当然这些小组在多数情况下只是一个人-- 1.外链小组 外 链小组主要网站项目的外

浅析document.createDocumentFragment()与js效率

对于循环批量操作页面的DOM有很大帮助!利用文档碎片处理,然后一次性append,并且使用原生的javascript语句操作   document.createDocumentFragment()说白了就是为了节约使用DOM.每次JavaScript对DOM的操作都会 改变页面的变现,并重新刷新整个页面,从而消耗了大量的时间.为解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片的内容一次 性添加到document中.这是我写的一个简单的测试页面: 复制代码 代码如下: <

浅析document.createDocumentFragment()与js效率_javascript技巧

document.createDocumentFragment()说白了就是为了节约使用DOM.每次JavaScript对DOM的操作都会改变页面的变现,并重新刷新整个页面,从而消耗了大量的时间.为解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片的内容一次性添加到document中.这是我写的一个简单的测试页面: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&quo

linux进程调度浅析

操作系统要实现多进程,进程调度必不可少. 有人说,进程调度是操作系统中最为重要的一个部分.我觉得这种说法说得太绝对了一点,就像很多人动辄就说"某某函数比某某函数效率高XX倍"一样,脱离了实际环境,这些结论是比较片面的. 而进程调度究竟有多重要呢? 首先,我们需要明确一点:进程调度是对TASK_RUNNING状态的进程进行调度(参见<linux进程状态浅析>).如果进程不可执行(正在睡眠或其他),那么它跟进程调度没多大关系. 所以,如果你的系统负载非常低,盼星星盼月亮才出现一

linux内核SMP负载均衡浅析

需求 在<linux进程调度浅析>一文中提到,在SMP(对称多处理器)环境下,每个CPU对应一个run_queue(可执行队列).如果一个进程处于TASK_RUNNING状态(可执行状态),则它会被加入到其中一个run_queue(且同一时刻仅会被加入到一个run_queue),以便让调度程序安排它在这个run_queue对应的CPU上面运行. 一个CPU对应一个run_queue这样的设计,其好处是: 1.一个持续处于TASK_RUNNING状态的进程总是趋于在同一个CPU上面运行(其间,这