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

条款19:了解临时对象的来源

1、C++真正的临时对象是不可见的,临时对象通常发生于两种情况:一是当隐式类型转换被施行起来使函数调用能够成功;二是当函数返回对象时。

2、只有当对象通过传值方式传递对象或当对象被传递给一个常量引用参数时,才会发生类型转换。如果对象被传递一个非常量引用参数,并不会发生类型转换。

例子:

#include<string>
#include<iomanip>
#include<iostream>
using namespace std;
//返回ch在str中的出现个数
size_t countChar(const string& str, char ch){
	size_t count = 0;
	for (int i = 0; i < str.length(); ++i){
		if (str[i] == ch)
			++count;
	}
	return count;
}
//将str中的所有chars改为大写
void uppercasify(string& str){
	for (int i = 0; i < str.length(); ++i){
		if (str[i] >= 'a'&&str[i] <= 'z')
			str[i] += ('A' - 'a');
	}
}
int main(){
	const int MAX_STRING_LEN = 200;
	char buffer[MAX_STRING_LEN];
	char c;
	cin >> c >> setw(MAX_STRING_LEN) >> buffer;
	//调用countChar函数时会产生一个类型为string的临时对象
	cout << "There are " << countChar(buffer, c) << " occurrences of the character "
		<< c << " in " << buffer << endl;

	char subtleBookPlug[] = "Effective C++";
	//uppercasify(subtleBookPlug);//错误,不会产生临时对象
	string subtle = "Effective C++";
	uppercasify(subtle);
	cout << subtle << endl;

	system("pause");
	return 0;
}

C++语言禁止为非常量引用参数产生临时对象的原因是当程序员期望修改非临时对象时,如果编译器对非常量引用对象进行隐式类型转换,却修改了临时对象,而原来的那个对象并未受到影响。

3、第二种会产生临时对象的情况是当函数返回一个对象时。

例子:

http://blog.csdn.net/ruan875417/article/details/47260827条款24中例子

#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; }
private:
	int numerator;
	int denominator;
};
const Rational operator*(const Rational& lhs, const Rational& rhs){
	return Rational(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
}
int main(){
	Rational oneEighth(1, 8);
	Rational oneHalf(1, 2);
	Rational result = oneHalf*oneEighth;
	result = oneHalf * 2;//正确
	result = 2 * oneHalf; //正确

	system("pause");
	return 0;
}

上述例子中const Rational operator*(const Rational& lhs, const Rational& rhs)函数的返回值是个临时对象,每当调用它时便得为此对象付出构造和析构成本。

总结:临时对象可能很耗成本,所以应该尽可能消除它们。任何时候只要看到常量引用参数,就很有可能会有一个临时对象被产生出来绑定在该参数上。任何时候只要看到函数返回一个对象,就会产生一个临时对象(并与稍后销毁)。


条款20:协助完成“返回值优化(RVO)”

1、如果函数一定得以传值方式传回对象,绝对无法消除之。

例子:

#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; }
private:
	int numerator;
	int denominator;
};
const Rational operator*(const Rational& lhs, const Rational& rhs){
	return Rational(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
}
int main(){
	Rational oneEighth(1, 8);
	Rational oneHalf(1, 2);
	Rational result = oneHalf*oneEighth;
	result = oneHalf * 2;//正确
	result = 2 * oneHalf; //正确

	system("pause");
	return 0;
}

上述例子const Rational operator*函数中以constructor arguments取代局部对象当做返回值,允许编译器将临时对象优化,使它们不存在。

Rational result = oneHalf*oneEighth;

这条语句使编译器能够消除“perator*内的临时对象”及“被operator*返回的临时对象”。 它们可以将return表达式所定义的对象构造于result的内存内。如果编译器这么做,调用operator*的临时对象的总成本就是0,也就是说没有任何临时对象需要被产生出来。

利用函数的return点消除一个局部临时对象的做法叫return value optimization。

returnvalue optimization可以参考我的另一篇博客http://blog.csdn.net/ruan875417/article/details/46438515中的三、程序转化语意学。


条款21:利用重载技术(overload)避免隐式型别转换(implicittype conversions)

条款20中main函数中

result = oneHalf * 2;//正确
result = 2 * oneHalf; //正确

这些语句也能正确执行是因为产生了临时对象,并将整数2转换为Rational。

可以利用重载技术避免隐式类型转换而产生的临时对象:

#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; }
private:
	int numerator;
	int denominator;
};
const Rational operator*(const Rational& lhs, const Rational& rhs){//Rational和Rational相乘
	return Rational(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
}
const Rational operator*(const Rational& lhs, int rhs){//Rational和int相乘
	return Rational(lhs.getNumerator()*rhs, lhs.getDenominator()*1);
}
const Rational operator*(int lhs, const Rational& rhs){//int和Rational相乘
	return Rational(lhs*rhs.getNumerator(), 1*rhs.getDenominator());
}
int main(){
	Rational oneEighth(1, 8);
	Rational oneHalf(1, 2);
	Rational result = oneHalf*oneEighth;
	result = oneHalf * 2; //正确,不会产生临时对象
	result = 2 * oneHalf; //正确,不会产生临时对象

	system("pause");
	return 0;
}

上述例子消除了类型转换。

但如果声明const Rational operator*(int lhs, int rhs);却是错的,因为C++的其中一条规则是每个重载操作符必须获得至少一个“用户定制类型”的自变量。int不是用户定制类型。

必须谨记80-20规则(参见条款16)。没有必要实现一大堆重载函数,除非有理由相信使用重载函数后,程序的整体效率可获得重大的改善。

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

posted on 2015-08-31 10:08  ruan875417  阅读(128)  评论(0编辑  收藏  举报

导航