如何提高C++的效率

上一篇 / 下一篇  2012-10-23 13:30:14 / 个人分类:C++

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

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

cW7HX0Nf%mS9G`0

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

q-Q Z[;K2[o6_0

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

8|*Z n jF_r `J^0

rpiJ!p|S0class X51Testing软件测试网 v![r7QK1c
{51Testing软件测试网*P-rEYA^R+z,HA\
public:
O+F[HR%q0virtual ~X(); //析构函数
5y%Ah;rq0virtual void VirtualFunc(); //虚函数51Testing软件测试网!R;k1tO-[#~cN$G
inline int InlineFunc() { return m_iMember}; //内联函数51Testing软件测试网S.m'~8Am Z%}]
void NormalFunc(); //普通成员函数51Testing软件测试网}9f F]8Tnne w:B s
static void StaticFunc(); //静态函数
#X Ab[ZBy$S:B0private:51Testing软件测试网;J;i[KT
int m_iMember;

Pqh&Hu#t y051Testing软件测试网I N(j_8KW;s

};

^8UZ3O` ki051Testing软件测试网2~l`9w9Y4W

class XX: public X
&u|-\.O.H6PS(MA0{
'X4PQ/O3Di0public:51Testing软件测试网Q3P$ywA1A4oO,Z
XX();
6[#L(l^*i*p0virtual ~XX();51Testing软件测试网dA2Z/Y3s(l9vkO
virtual void VirtualFunc();51Testing软件测试网3c#y*q7zOn]}jo
private:
x} A O dKy^'x0String m_strName;51Testing软件测试网o ]E8Y@
int m_iMember2;

0s,aP"oy;H_2K0

iY7BOX0};51Testing软件测试网3N3i%s6AU&e/Q$OdD

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

fD6ShIH051Testing软件测试网$CX9{v^R'P*_Y

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

f u/Z r8^`5`T&X051Testing软件测试网a4C2i z]@4r#j

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

)JY&_,f#M bd%H0X obj; X* ptr = &obj;51Testing软件测试网E.UFnnJ0]
obj.StaticFunc();51Testing软件测试网4\_.uq\)HQD
ptr->StaticFunc();51Testing软件测试网 z ] z"xrG
X::StaticFunc();
IqjU1k]a J0

]-lE,I(Cls'Kt|0  将被编译器转化为一般的C函数调用形式,如同这样:

8e1~ [zc*L3@1w051Testing软件测试网@J+T)_+vf

aZ4e[?n-h0]0
mangled_name_of_X_StaticFunc();
g9t)k{ l'K TN1k GM0//obj.StaticFunc();
0y'| JW%zu$D0mangled_name_of_X_StaticFunc();
wZ TL1C.K%U0// ptr- >StaticFunc();
{0x1d&d&u7?w }0mangled_name_of_X_StaticFunc();51Testing软件测试网p/FJ,Rr?&~6nT'\
// X::StaticFunc();
51Testing软件测试网Lv'^4Z!r7i

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

g0BM~zM[-h+u.m051Testing软件测试网Vm%rao3u[

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

{7Gf,{-L w\1i7V0z0

7bp*s[O jt0

d} O!Yope0
X obj; X* ptr = &obj;
J(VCo%MQ'_ xe;\0obj.NormalFunc();51Testing软件测试网%T#Ep0X+?:^?9z
ptr->NormalFunc();

v4?dp4t0  将被被编译器转化为如下的C函数调用形式,如同这样。51Testing软件测试网!@N'h7LvJ;U0u

]L)VXp$f5kv051Testing软件测试网c o0_]4E*?K'v

mangled_name_of_X_NormalFunc(&obj);
:m8W5y&s1Js0//obj.NormalFunc();51Testing软件测试网a9XI9f K q
mangled_name_of_X_NormalFunc(ptr);51Testing软件测试网$b,S t-~ b
// ptr- >NormalFunc();

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

m I0ja!`y051Testing软件测试网Eb&wjw1C9Y

  虚函数的调用稍微复杂一些,为了支持多态性(其基础是函数重载),实现运行时刻绑定,编译器需要在每个对象上增加一个字段也就是vptr以指向类的虚函数表vtbl。51Testing软件测试网BE4SE%L*b cYCx;n;z

.b ^i;j3af[!B1B[0  虚函数的多态性只能通过对象指针或对象的引用调用来实现,如下的调用:

J'nvB&F*?l0

+T;tf@/ZD051Testing软件测试网&v3Oa"UE.\7~E

X obj;51Testing软件测试网Fn-q6j|;l|O4Y$N
X* ptr = &obj; X& ref = obj;51Testing软件测试网_(C7Mk O!X&j\%]
ptr->VirtualFunc();51Testing软件测试网"|{]*R%]Mc
ref.VirtualFunc();
51Testing软件测试网i3BM-G+G d-v

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

3]iT0x.TLg#e4Tej@0v0

0R&U2M"fV051Testing软件测试网\ig:@7}'ZG2K#n

( *ptr->vptr[2] )(ptr);
%Yr6F"@:?!Mz/F@#L0( *ptr->vptr[2] )(&ref);

;a3z,zq#O2vq t:\0  其中的2表示VirtualFunc在类虚函数表的第2个槽位。可以看出,虚函数的调用相当于一个C的函数指针调用,其效率也并未降低。51Testing软件测试网0Y6p|)t)eT^*gn^l

51Testing软件测试网"?h:K VW5}(k2~

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

r7[R? gCL'Gy0

6ju'[ge051Testing软件测试网5o3N4G*].R)N6F(Tt_]!u

md8GBh*p"k*r0XX::XX()51Testing软件测试网)YWRFq{L,Xc'a z
{
E"k@ h u$hAP3x0// 编译器扩充代码所要做的工作

+gJ$X0lNG,wl0

~\+}3q*T)T!O01、调用父类X的缺省构造函数51Testing软件测试网 a^'[di:d R
2、设定vptr指向XX类虚函数表
${;TJ-wm Y IbB03、调用String类的缺省构造函数构造m_strName
+FsBj?.W7xg!s0};

DB0T|}Fv0
51Testing软件测试网4L.XP6Rj\
51Testing软件测试网l?Z oG h9K

  所以为了提高效率,减少不必要的临时对象的产生、拖延暂时不必要的对象定义、用初始化代替赋值、使用构造函数初始化列表代替在构造函数中赋值等方法都能有效提高程序的运行效率。以下举例说明:51Testing软件测试网~ H(X?+Pq(^c9c/CTH

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

%b^6M }2y|`051Testing软件测试网HXSg!P!q \ysD:|/h

  效率不高的做法               高效率做法

v a N|u*f051Testing软件测试网TgsC#p ~

  void Function( XX xx )       void Function( const XX& xx)
|6b|.CS1nN0  {                                {
e8vW8]%S,DW2@9q6bP:t0  //函数体                              //函数体51Testing软件测试网 nv ]:te@
  }                                 }
51Testing软件测试网3lR'iFA}

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

dOx6o w*f0

z#]5i7o]1b7~)~\7vmf&u0  效率不高的做法                                          高效率做法51Testing软件测试网{mt+e0bQs

/{BX;S9u0  void Function( bool bCache )                 void Function( bool bCache )51Testing软件测试网 G\ C7T+YAv+D
  {                                                      {51Testing软件测试网3YQy7?T z6^&KK
  //函数体                                                    //函数体51Testing软件测试网N7x#A6rwf~Q3O!}5tu!m
  XX xx;
M q6J|8U6H,~0  if( bCache )
{n Dee~`L0   if( bCache ) {// do something without xx{                                                                                                    return;51Testing软件测试网8?*J%z'P5X^2j/Q
  // do something without xx}
_.yu t X;S^jg5xN#i3fwP0  return;                                                  }
K5D)yQRHO y0  }51Testing软件测试网;v/JCI1b ls
  //对xx进行操作XX xx;
r4GgY w0N1k+l0  //对xx进行操作51Testing软件测试网*V2@0b/o*h2w
  …
Y]v ?-k5Ejj0  return;                                                        return;
?9g(`2K;\3Q/_V0H0  }                                                        }

D$` qME0

KS5A0d2{"h$j;}A)R/H0  3、可能情况下,以初始化代替先定义后赋值。如下例,高效率的做法会比效率不高的做法省去了cache变量的缺省构造函数调用开销。

\}nQ H0[\+T1`051Testing软件测试网%Z5lvL4jfn)S

  效率不高的做法                                                高效率做法

6|)|0U/lzkNhg7^051Testing软件测试网(G#y(r(k2nF6f

  void Function( const XX& xx )                    oid Function( const XX& xx )(传引用可以省去临时对象构造和析构的过程)51Testing软件测试网Z8]o])UC
  {                                                {51Testing软件测试网{@$Kb$CV
  XX cache;     (调用了构造函数)                         XX cache = xx;(只调用了复制构造函数)
%a i] Z6_7Nm;_cO0  cache = xx ;  (调用了赋值函数)51Testing软件测试网y3{r7dl~U
  }                                                 }
51Testing软件测试网*?7vX aj0rG4ct

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

`Q}6Hz$e,g051Testing软件测试网`,];U&Rv

  效率不高的做法                                           高效率做法51Testing软件测试网7_+L%cF*U m6y

51Testing软件测试网:S#p*~'g'H6?

  XX::XX()                                            XX::XX() : m_strName( "" )
-G1p!}%jS#M1n0  {                                                    {
[ `6h0\p.Ez8@qL%{0  m_strName = ""; …51Testing软件测试网k,@9YQ2yL&ESh(j
  …51Testing软件测试网;w!m:]t0iCk~d(U
  }                                                     }
51Testing软件测试网~3V's~#hiD%P8j

51Testing软件测试网 yc-nMP1oP

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

Mf0wy!r%r(uV0
r)HE4g+x6Y zH4i3G0

TAG:

 

评分:0

我来说两句

Open Toolbar