《More Effective C++》读书笔记-效率

来源:互联网 发布:教育过程最优化 编辑:程序博客网 时间:2024/05/19 05:01

条款16-18省略。

19、了解临时对象的来源

临时对象产生的两种条件:

1、为了使函数成功调用而进行隐式类型转换时。
2、函数返回对象时。

临时对象是有开销的,因此要尽可能去消除它们,然而更重要的是训练自己寻找可能建立临时对象的地方,在任何时候只要见到常量引用参数,就存在建立临时对象而绑定在参数上的可能性,在任何时候只要见到函数返回对象,就会有一个临时对象被建立(以后被释放)。

20、协助完成返回值优化

略过。

21、通过重载避免隐式类型转换

隐式类型转换将产生临时对象,从而带来额外的系统开销。

解决办法是使用重载,以避免隐式类型转换。要注意的一点是在C++中有一条规则是每一个重载的operator必须带有一个用户定义类型的参数(这条规定是有道理的,如果没有的话,程序员将能改变预定义的操作,这样做肯定把程序引入混乱的境地)。
另外,牢记8020规则,没有必要实现大量的重载函数,除非有理由确信程序使用重载函数后整体效率会有显著提高。

class UPint  {  public:      UPint();      UPint(int value);      ....  }  //重载operator+(const UPint& lhs,const UPint& rhs)const UPint operator+(const UPint& lhs,const UPint& rhs);  UPint u1,u2;  UPint u3 = u1 + u2;//都是UPint对象,利用上述重载operator+即可//以下两种情形也可成功,为什么?因为以下语句执行了隐式类型转换,产生了//临时对象,并将整数10转换为UPint。UPint u4 = 10+u2;UPint u4 = u2+10; 

编译器来执行此类隐式转换,很方便,但是此类转换会产生临时对象,从而产生我们不想要的运行成本。我们并不想进行隐式类型转换,仅仅想要能够对UPint和int进行加法,可以利用重载函数,每个函数拥有不能的参数。如下列所示:

const UPint operator+(cosnt UPint& lhs,int rhs);  const UPint operator+(int lhs,const UPint& rhs);  

22、考虑以操作符复合形式(op=)取代其独身形式(op)

要确保操作符的复合形式和其独身形式之间的自然关系能够存在,一个好方法就是以复合形式为基础来实现独身形式

Rational& operator+=(const Rational& rhs);//(1)const Ratinal operator+(const Rational& lhs,const Rational& rhs)  {//(2)    return Rational(lhs)+=rhs;  }  

此例中operator+=是从头做起的,而operator+是调用前者以供应它们所需的机能。如果采用这种设计,那么这些操作符之中就只有复合形式才需要维护。 主要有以下好处:

1、一般而言,复合操作符比独身版本效率高,因为独身版本通常必须返回一个新对象,故必须要负担一个临时对象的构造和析构成本。至于复合版本则是直接将结果写入其左端自变量,所以不需要产生一个临时对象来放置返回值。

2、允许客户在效率和便利之间作取舍。

Rational a,b,c,d,result;  //(1)独身版本:便利。用到三个临时对象。result = a+b+c+d;   //(2)复合版本:效率。都不需要临时对象。 result=a;  result+=b;  result+=c;  result+=d;  

前者较易撰写、调试、维护。后者效率较高。

3、独身版本的两个实现形式:

template<class T>  const T operator+(const T& lhs,const T& rhs)  {      return T(lhs) += rhs; //产生临时对象}    //version 2template<class T>  const T operator+(const T& lhs,const T& rhs)  {      T result(lhs);//产生命名对象    return result+= rhs;  }  

第一个版本和第二版本几乎一样,但是却有个重要的差异,第二版本中含有一个命名对象result,而第一版本中拥有返回值优化,使得编译器具有最佳的效果。匿名对象总是比命名对象更容易消除,所以当你面临命名对象和临时对象的抉择时,最好是选择临时对象。它绝不会比其命名对象耗用更多成本,反倒是极有可能降低成本。

23、考虑使用其它程序库

略过。

24、了解 virtual functions、multiple inheritance、virtual base classes、runtime type identification 的成本

1、使用虚函数,会使用所谓的 virtual tables 和 virtual table pointers ,通常简写为 vtbls 和 vptrs 。vtbl通常是一个函数指针的数组或链表,每一个声明或继承虚函数的类都有自己的vtbl,其中的每一个元素就是该类的各个虚函数的指针。

虚函数所需的代价:
(1)必须为每个包含虚函数的类的virtual table留出空间;
(2)每个包含虚函数的类的对象里,必须为额外的指针付出代价;
(3)实际上放弃了使用内联函数,虚函数是运行时绑定的,而 inline 是编译时展开的,即使你对虚函数使用 inline ,编译器也通常会忽略。

2、多继承时,在单个对象里有多个vptr(一个基类对应一个),”找出对象内的vptrs”会变得比较复杂,它和虚基类一样,会增加对象体积的大小。

多重继承往往导致virtual base classes(虚拟基类)的需求。在non-virtual base的情况下,如果派生类对于基类有多条继承路径,那么派生类会有不止一个基类部分,让基类为virtual可以消除基类的数据成员在每一个子类复制滋生。然而虚基类也可能导致另一成本:其实现做法常常利用指针,指向”virtual base class”部分,因此对象内可能出现一个(或多个)这样的指针。例如多重继承的”菱形”结构。

3、RTTI能让我们在运行时找到对象和类的有关信息,所以肯定有某个地方存储了这些信息让我们查询,这些信息被存储在类型为type_info的对象里。通常,RTTI被设计为在类的vbtl上实现。
这里写图片描述

0 0
原创粉丝点击