【more effective c++读书笔记】【第4章】效率(3)

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

1、确保操作符的复合形式(如operator+=)和其独身形式 (如 operator+ )之间的自然关系能够存在,一个好方法就是以前者为基础实现后者。

例子:

#include<iostream>
using namespace std;

class Rational{
public:
	Rational(int n = 0, int d = 1) :numerator(n), denominator(d){}//构造函数刻意不为explicit,为了隐式类型转换
	int getNumerator() const{ return numerator; }
	int getDenominator() const{ return denominator; }
	Rational& operator*=(const Rational& rhs){
		this->numerator *= rhs.numerator;
		this->denominator *= rhs.denominator;
		return *this;
	}
private:
	int numerator;
	int denominator;
};
const Rational operator*(const Rational& lhs, const Rational& rhs){//Rational和Rational相乘
	return Rational(lhs) *= rhs;
}
ostream& operator<<(ostream& os, Rational& r){
	os << r.getNumerator() << '/' << r.getDenominator();
	return os;
}

int main(){
	Rational oneEighth(1, 8);
	Rational oneFourth(1, 4);
	Rational oneHalf(1, 2);
	Rational result = oneHalf*oneFourth*oneEighth;
	cout << result << endl;

	system("pause");
	return 0;
}

3个与效率有关的情况需要注意:

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

第二,如果同时提供某个操作符的复合形式和独身形式,便允许客户在效率与便利性之间做取舍。客户端可以决定是这样编写:

Rational a, b, c, d, result;
...
result = a * b * c * d; // 可能用了3个临时对象,每个operator* 调用使用1个
或是这样编写:
result = a;  //不用临时对象
result *= b; // 不用临时对象
result *= c; //不用临时对象
result *= d;//不用临时对象

第三,编译器一般对匿名对象进行返回值优化,而对命名对象通常不能。

const Rational operator*(const Rational& lhs, const Rational& rhs){//Rational和Rational相乘
	//实现一
	return Rational(lhs) *= rhs;
	//实现二
	Rational result(lhs);
	return result *= rhs;
}

上述例子实现二内含一个命名对象result,返回值优化无法施展于此operator*实现代码身上,实现一总是适用返回值优化。


条款23:考虑使用其他程序库

考虑iostream和stdio程序库,iostream与stdio相比有几个优点:例如它具有类型安全的特性并且可扩充。然而在效率方面,iostream通常表现得比stdio差,因为stdio的可执行文件通常比iostream更小也更快。

不同的程序库即使提供相似的技能,也往往表现出不同的性能取舍策略,所以一旦你找出程序的瓶颈,应该思考是否可能通过改用另一个程序库来消除瓶颈。


条款24:了解virtual functions、multiple inheritance、virtual base classes、runtime typeidentification的成本

1、虚函数会带来以下成本:

a、你必须为每个拥有虚函数的class耗费一个virtual talbe空间,其大小视虚函数的个数(包括继承而来的)而定。

b、你必须在每一个拥有虚函数的对象内付出一个为额外指针的代价,也就是一个virtual table pointer。

c、你事实上放弃了inlining。因为“inline”意味“在编译期,将调用端的调用动作被调用函数的函数本体取代”,而“virtual”意味“直到运行时期才能知道哪个函数被调用”。

2、多重继承会更复杂:

a、找出对象内的vptrs会变得比较复杂。因为一个对象之内会有多个vptrs;除了我们已经讨论过的单独的vtbl以外,针对base classes而形成的特殊vtbls也会被产生出来。因此虚函数对每一个对象和每一个类所造成的空间负担又增加了,运行时期的调用成本也增加了。

b、多继承经常导致对虚基类的需求。虚基类可能导致另一个成本,因为其实现做法常常利用指针,指向虚基类成分,以消除重复现象,这样可能导致对象内的隐藏指针增加。

3、RTTI让我们在运行时期获得对象和类的相关信息,这些信息被存储在类型为type_info的对象里,通过typeid操作符可以获取一个类的type_info对象。C++规范书上说,只有当某种类型拥有至少一个虚函数,才保证我们能够检验该类型对象的动态类型。RTTI的设计理念是根据类的vtbl来实现。

上述内容的具体例子见我的另一篇博客http://blog.csdn.net/ruan875417/article/details/46408475

版权声明:本文为博主原创文章,未经博主允许不得转载。

posted on 2015-09-01 14:17  ruan875417  阅读(161)  评论(0编辑  收藏  举报

导航