读effetive c++笔记之对象传递和对象返回

先写出关于对象传递和对象返回的总结:相对函数来说,如果是传递对象请使用pass-by-reference 而对象返回请使用pass-by-value.


  为什么对象传递的时候要用pass-by-reference,而不用pass-by-value呢,举个例子,如下:

#include<iostream>
#include<string>
using  namespace std;

class Person {
public:
	Person(string _name, string _address):name(_name), address(_address) {
	}
	~Person() {}
private:
	string name;
	string address;
};

class Student:public Person {
public:
	Student(string _name, string _address, string _schoolName, string _schoolAddress)
		:Person(_name, _address), schoolName(_schoolName), schoolAddress(_schoolAddress){
	}
	~Student() {}
private:
	string schoolName;
	string schoolAddress;
};

Student student("tom", "yilu", "mit", "America");

bool isValidateStudent(Student s) {
	// to do
}


当把上面的student对象作为参数传递给下面的:isValidateStudent函数的时候会发生什么?Student的默认拷贝构造函数会构造一个student对象的副本s,并将student中存储的内容复制到副本对象s中,当函数结束的时候,会对这个副本调用其析构函数,之后我们在分析一下Student内部的结构,算上从Person继承的成员变量,它一共内涵4个string成员变量,所以在把student对象内容复制给s的时候,要调用4次string对象的构造函数,在再有析构s的时候,要调用4次的string的析构函数,所以加上对s的Person部分的构造,一共要调用6次构造函数和6次析构函数。这样看来,进行值传递的代价是不是很大了。但是如果我们使用pass-by-reference进行传递的话,就不用有pass-by-value那样的开销,但是调用函数isValidateStudent的人也许不放心,因为他怕函数的内部改变了student原有的值,所以在引用传递的时候,要加上const,防止传递的对象被意外更改。

pass-by-value会引起的另一个问题就是对象切割,还是以上面那个例子做基础,做稍微的更改,如下:

#include<iostream>
#include<string>
using  namespace std;

class Person {
public:
	Person(string _name, string _address):name(_name), address(_address) {
	}
	~Person() {}
	void liveWhere() {
		cout << address << endl;
	}
	virtual void likingWhat();
private:
	string name;
	string address;
};

class Student:public Person {
public:
	Student(string _name, string _address, string _schoolName, string _schoolAddress)
		:Person(_name, _address), schoolName(_schoolName), schoolAddress(_schoolAddress){
	}
	~Student() {}
	virtual void likingWhat() {
		// to do 
	}
private:
	string schoolName;
	string schoolAddress;
};

Student student("tom", "yilu", "mit", "America");

void isLikingWhat(Person p) {
	cout << p.likingWhat() << endl;
}

上面的例子假设社会上的每一个群体都有自己独特的喜好,那么函数isLikingWhat将接受一个基类Person的对象,并打印出每个对象所喜欢的东西,可以看出,这是对应多台的应用。然而当传递student对象给isLikingWhat函数的时候,他不知道应该怎么来构造这个对象,因为他的参数类型是Person,它不能确定,继承于Person的子类对象是什么,所以在参数传递的时候,只有student对象的Person部分被复制,而属于student专有的内容被抛弃了,则在isLikingWhat函数进行打印的时候他调用的是Person的函数,而不是Student实现的函数。这就是pass-by-value造成的对象切割。如果我们换成引用传递的话,我们应该还记得,子类的对象是可以用来给父类对象的引用赋值的。所以当在引用传递的时候,会根据虚函数列表找到相应的函数进行调用。

总结一下:pass-by-reference解决了两个pass-by-value不能解决的问题,一个是构造函数和析构函数调用引起的资源浪费,另一个是对象切割。

既然能看到pass-by-reference这么多好处,那么我们在函数返回值的时候也考虑一下使用引用返回,怎么样呢,因为在函数返回值的时候要进行对象拷贝,把返回的值拷贝到接收对象之中,如果让接收对象只是一个引用,那么就省去了构造函数和析构函数调用所带来的代价。

考虑下面这样一个例子:

#include<iostream>
#include<string>
using  namespace std;

class Rational {
public:
	Rational(int _n, int _d) {
		n = _n;
		d = _d;
	}
	~Rational() {
	}
	const Rational& operator*(const Rational& lhs, const Rational &rhs);
private:
	int n;
	int d;
};

const Rational& Rational::operator *(const Rational &lhs, const Rational &rhs) {
	Rational result = Rational(lhs.n * rhs.n, lhs.d * rhs.d);
	return result;
}

可以看看这个operator*函数,首先它建立了一个Rational对象,因为是函数的内部对象,所以应该放在栈中,之后函数返回对对象result的引用。想想会出现什么问题,当oper*函数结束的时候,result对象也被析构了,它在内存中不在存在,但是函数之外还是会对它进行引用,这就会像悬垂指针一样,乱指内存,当有其他函数应用它的时候,就会出现问题,或者造成整个程序的崩溃,(我写过这样的程序,因为野指针造成运行在手机上的程序崩溃,手机死机)。既然这样,那我们就会马上想到另外一种方法,就是用堆,这样,函数结束的时候,对象就会存在,引用它的时候就不会出问题了,看看下面的实现:

const Rational& Rational::operator *(const Rational &lhs, const Rational &rhs) {
	Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
	return *result;
}

把对象存储在堆上我们就放心了,但是还要考虑的问题就是,谁来释放我们分配在对象的对象呢,释放对象一个很好的时机就是没有人引用它的时候进行释放。那么请看下面这个例子:

Rational a, b ,c , d; 

d = a * b * c;

按照上面operator*的实现方法,new要被调用两次,但是只有一个引用来指向两次中最后一次分配的Rational,第一次分配的就没有人最管它,想释放的时候都不知道怎么释放了。

总结上面两点:如果将对象放在heap或者stack上都会造成问题,那么我们可以考虑另外一个方法,就是把对象放在可以长期保存又有人去管理的地方怎样呢-静态存储区。

实现如下:

const Rational& Rational::operator *(const Rational &lhs, const Rational &rhs) {
	static Rational result =Rational(lhs.n * rhs.n, lhs.d * rhs.d);
	return result;
}

这样实现,我们既不用考虑分配在stack上的问题,也不用考虑分配在heap上的问题,但是考虑下面这个调用:

bool operator==(const Rational &lhs, const Rational &rhs), 有Rational a, b, c, d; 
if (a * b == c  * d) {
} else {
}

大家能猜到结果么,结果就是不管a,b,c,d 是什么值,条件判断的结果总是真,来分析一下吧,operator返回的是静态变量的引用,那么不管怎么改变它的值,引用都是不知道的, 上面的条件判断中,一共调用了两次operator*, 第一次是a*b,结果存储的是a*b的值,而第二次是c*d的值,仔细想一想,operator*返回的引用总是指向最新的值,而不管它被调用了多少次,因为他是静态变量。


通过以上的分析,总结出一点:当向函数内部传递参数的时候,请尽量使用pass-by-reference,当从函数向外部传递的时候,请尽量使用pass-by-value,也就是说,请不要在接收函数返回值的时候吝啬调用构造函数和析构函数。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值