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;
}
問題:
- 這裏is_adult函數在調用時,傳遞了Student的值,底層操作會調用Student的拷貝構造函數,構造兩個string對象。當is_adult函數生命週期結束,這個構造出來的student會被析構掉。造成了很大的開銷,這些開銷在這個函數裏用傳引用的方式是完全可以避免的。
- 如果is_adult(MiddleSchoolStudent(…)) 這麼做,會產生切割問題,即調用的是 Student類的 print_type() 方法,因爲這就是個Student對象。
一些解釋:
注意,傳引用Student& student,在這裏我們不涉及對student內容的修改,因此我們最好的方式是加一個const Student&,即const引用來保證傳進來的對象不會被修改。這麼做有兩個好處:
- 避免構造函數析構函數的資源消耗,提升代碼效率。
- 避免出現切割問題,保持多態性
但是有些場景不適合,可以思考下爲什麼不適合:傳內置類型(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的開銷,但是這兩個方法都無法避免構造開銷。而且會造成嚴重問題,上面的函數命名其實已經很好的的說明了問題所在:
- 棧上的對象會在方法生命週期結束後銷燬。空指針(很不幸,我自己工作中就寫過這樣的Bug,就是爲了避免返回值)
- 堆上的對象返回引用,外面根本不知道什麼時候銷燬,造成內存泄露的可能性非常大。
一些解釋:
雖然書上沒有寫,但是其實有些地方是可以返回堆上的引用,可以在此討論下:
- 構造的時候,比如把複雜的構造封裝到一個函數裏了,保證最後能被析構。
- 另外可能傳入參數本身就是堆上的,那封裝一些對它的操作再返回這個引用也是可以的,不過這種操作最好還是封裝到對象本身。