Effective C++拾遺之條款20&21:值和引用雙刃劍

Good Part 傳引用

場景:

class Student {
public:
	string name;
	string description;
	uint32_t age;
	Student (...) {...}
	virtual print_type();
};
class MiddleSchoolStudent : public Student {
	// 實現了一種print_type()
}
bool  is_adult(Student student) {
	if (student.age >= 18) {
		student.print_type();
		return true;
	}
	return false;
}

問題:

  1. 這裏is_adult函數在調用時,傳遞了Student的值,底層操作會調用Student的拷貝構造函數,構造兩個string對象。當is_adult函數生命週期結束,這個構造出來的student會被析構掉。造成了很大的開銷,這些開銷在這個函數裏用傳引用的方式是完全可以避免的。
  2. 如果is_adult(MiddleSchoolStudent(…)) 這麼做,會產生切割問題,即調用的是 Student類的 print_type() 方法,因爲這就是個Student對象。

一些解釋:

注意,傳引用Student& student,在這裏我們不涉及對student內容的修改,因此我們最好的方式是加一個const Student&,即const引用來保證傳進來的對象不會被修改。這麼做有兩個好處:

  1. 避免構造函數析構函數的資源消耗,提升代碼效率。
  2. 避免出現切割問題,保持多態性

但是有些場景不適合,可以思考下爲什麼不適合:傳內置類型(int, double …),STL的迭代器,函數對象(這個我暫時沒遇到過)

Bad Part 返回引用

場景:

複用上面的聲明

Student* make_a_stack_good_student(const Student& student) {
	Student good_student(student);
	return &good_student;
}
Student* make_a_heap_good_student(const Student& student) {
	Student* good_student = new Student(student);
	return good_student;
}

問題:

首先明確我們在Good Part裏傳引用的原因1是說減少構造zhan的開銷,但是這兩個方法都無法避免構造開銷。而且會造成嚴重問題,上面的函數命名其實已經很好的的說明了問題所在:

  1. 棧上的對象會在方法生命週期結束後銷燬。空指針(很不幸,我自己工作中就寫過這樣的Bug,就是爲了避免返回值)
  2. 堆上的對象返回引用,外面根本不知道什麼時候銷燬,造成內存泄露的可能性非常大。

一些解釋:

雖然書上沒有寫,但是其實有些地方是可以返回堆上的引用,可以在此討論下:

  1. 構造的時候,比如把複雜的構造封裝到一個函數裏了,保證最後能被析構。
  2. 另外可能傳入參數本身就是堆上的,那封裝一些對它的操作再返回這個引用也是可以的,不過這種操作最好還是封裝到對象本身。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章