C++進階(語法篇)—第10章 智慧指標
10.智慧指標
10.1 unique_ptr
先看下面的程式碼:
#include <iostream>
using namespace std;
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
int
cout << "*iptr = " << *iptr << endl;
return;
}
當Smart_pointer函式執行完後,由於沒有delete iptr(忘了寫),因此iptr被銷燬,而iptr指向的那塊堆記憶體依舊存在,造成了記憶體洩漏。
注:當main函式執行完後,系統會自動清理堆空間;但是有的程式main函式會一直執行。
#include <iostream>
using
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
int * iptr = new int(a);
cout << "*iptr = " << *iptr
//...發生異常...
delete iptr;
return;
}
當Smart_pointer函式未執行到delete處發生了異常,導致未釋放堆空間造成記憶體洩漏。
注:在異常那一章講解了這個問題,可以再catch和throw一次;但是不能每個函式都這麼處理,這樣太耗費資源。
#include <iostream>
using namespace std;
class SmartPointer {
private:
int * sp;
public:
SmartPointer():sp(NULL){}
SmartPointer(int * sp)
{
cout << "SmartPointer(int * sp)" << endl;
this->sp = sp;
}
~SmartPointer()
{
cout << "~SmartPointer()" << endl;
if (sp)
delete sp;
}
int operator*()
{
return *sp;
}
};
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
//int * iptr = new int(a);
//cout << "*iptr = " << *iptr << endl;
//delete iptr;
SmartPointer sp = new int(a);
cout << "*sp = " << *sp << endl;
return;
}
執行結果:
SmartPointer(int * sp)
*sp = 1
~SmartPointer()
執行了解構函式,使得sp被delete,即堆記憶體被釋放。
為什麼加上類SmartPointer 就可以了呢?
當執行SmartPointer sp = new int(a),實際分為2步:int * iptr = new int(a)和SmartPointer sp = iptr。當函式Smart_pointer執行完後,會銷燬SmartPointer類的物件sp,呼叫sp類的解構函式,因此可以在解構函式中delete,這樣會沒有了記憶體洩漏問題。
其實在C++11的庫中,提供一個智慧指標模板unique_ptr,這樣就不需要我們自己來提供智慧指標。它需要包含標頭檔案memory(被標頭檔案iostream包含了)和使用名稱空間std。
#include <iostream>
using namespace std;
void Smart_pointer(int a);
int main(int argc, char ** argv)
{
Smart_pointer(1);
return 0;
}
void Smart_pointer(int a)
{
unique_ptr<int> up_i(new int(a));
cout << " *up_i = " << *up_i << endl;
//不需要delete
return;
}
執行結果:
*up_i = 1
智慧指標模板unique_ptr只能處理一個指標指向同一個物件的問題。如:
unique_ptr<int> up_i1(new int(a));
unique_ptr<int> up_i2;
up_i2 = up_i1;//編譯出錯
那麼當多個指標指向同一個物件時,該怎麼辦呢?
10.2 shared_ptr
#include <iostream>
using namespace std;
class RefBase {
private:
int count;
public:
RefBase():count(0) {}
void incStrong() { count++; }
void decStrong() { count--; }
int getCount() { return count; }
};
template<typename T>
class SmartPointer {
private:
T * sp;
public:
SmartPointer() :sp(NULL) {}
SmartPointer(T * sp)
{
cout << "SmartPointer(T * sp)" << endl;
this->sp = sp;
sp->incStrong();
}
SmartPointer(const SmartPointer & c_sp)
{
cout << "SmartPointer(const SmartPointer & c_sp)" << endl;
this->sp = c_sp.sp;
this->sp->incStrong();
}
~SmartPointer()
{
cout << "~SmartPointer()" << endl;
if (sp)
{
cout <<"count = "<< sp->getCount() << endl;
sp->decStrong();
if(sp->getCount() == 0)
delete sp;
}
}
T operator*()
{
cout << "T operator*()" << endl;
return *sp;
}
};
class Student:public RefBase
{
private:
const char * name;
int age;
public:
Student() :name("no name"), age(0) { cout << "Student()" << endl; }
Student(const char * name, int age)
{
cout << "Student(const char * name, int gae)" << endl;
this->name = new char[strlen(name) + 1];
strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);
this->age = age;
}
~Student()
{
if (this->name)
{
if (this->getCount() == 0)
{
cout << "~Student()" << endl;
delete this->name;
}
}
}
void pintf(void)
{
cout << "name: " << this->name << endl << "age:" << this->age << endl;
}
};
void test_sp(SmartPointer<Student>& a);
int main(int argc, char ** argv)
{
SmartPointer<Student> sp_stu = new Student("zhangsan",16);
test_sp(sp_stu);
return 0;
}
void test_sp(SmartPointer<Student>& a)
{
SmartPointer<Student> sp = a;
(*sp).pintf();
return;
}
執行結果:
Student(const char * name, int gae)
SmartPointer(T * sp)
SmartPointer(const SmartPointer & c_sp)
T operator*()
name: zhangsan
age:16
~SmartPointer()
count = 2
~SmartPointer()
count = 1
~Student()
在main函式中使用new建立的物件,有main()中的sp_stu和test_sp()中的sp指向它,共2個指標指向同一個物件。
我使用了類模板對上一個類SmartPointer進行了改進,使它更具有重用性。建立了基類RefBase進行引用計數,每引用一次count加1,析構一次count減1,當count為0時delete。
問題1:為什麼拷貝建構函式中對count加1,加的卻是原來Student物件的count?
因為這裡的拷貝建構函式,僅僅是地址的複製,即是淺拷貝而不是深拷貝,它指向的還是原來的Student物件。
問題2:為什麼要把引用計數放到RefBase中,而不是類SmartPointer中?
因為對於每一個物件都要有一個引用計數,它最好和實際物件繫結。若放在SmartPointer中,當test_sp執行完後,由於臨時變數SmartPointer<Student> sp的銷燬,導致呼叫Student的解構函式,delete掉資料成員name,最後執行失敗。因此在Student的解構函式中增加對count的判斷。
問題3:難道我們要自己提供和維護這樣的RefBase類和SmartPointer類嗎?有沒有別的辦法?
使用C++11庫中的shared_ptr。它需要包含標頭檔案memory(被標頭檔案iostream包含了)和使用名稱空間std。
#include <iostream>
using namespace std;
class RefBase {
private:
int count;
public:
RefBase():count(0) {}
void incStrong() { count++; }
void decStrong() { count--; }
int getCount() { return count; }
};
template<typename T>
class SmartPointer {
private:
T * sp;
public:
SmartPointer() :sp(NULL) {}
SmartPointer(T * sp)
{
cout << "SmartPointer(T * sp)" << endl;
this->sp = sp;
sp->incStrong();
}
SmartPointer(const SmartPointer & c_sp)
{
cout << "SmartPointer(const SmartPointer & c_sp)" << endl;
this->sp = c_sp.sp;
this->sp->incStrong();
}
~SmartPointer()
{
cout << "~SmartPointer()" << endl;
if (sp)
{
cout <<"count = "<< sp->getCount() << endl;
sp->decStrong();
if(sp->getCount() == 0)
delete sp;
}
}
T operator*()
{
cout << "T operator*()" << endl;
return *sp;
}
};
class Student:public RefBase
{
private:
const char * name;
int age;
public:
Student() :name("no name"), age(0) { cout << "Student()" << endl; }
Student(const char * name, int age)
{
cout << "Student(const char * name, int gae)" << endl;
this->name = new char[strlen(name) + 1];
strcpy_s(const_cast<char *>(this->name), strlen(name) + 1, name);
this->age = age;
}
~Student()
{
if (this->name)
{
if (this->getCount() == 0)
{
cout << "~Student()" << endl;
delete this->name;
}
}
}
void pintf(void)
{
cout << "name: " << this->name << endl << "age:" << this->age << endl;
}
};
void test_sp(shared_ptr<Student>& a);
int main(int argc, char ** argv)
{
//SmartPointer<Student> sp_stu = new Student("zhangsan",16);
shared_ptr<Student> sp_stu(new Student("zhangsan", 16));
test_sp(sp_stu);
return 0;
}
void test_sp(shared_ptr<Student>& a)
{
shared_ptr<Student> sp = a;
(*sp).pintf();
return;
}
執行結果:
Student(const char * name, int gae)
name: zhangsan
age:16
~Student()
智慧指標模板shared_ptr適用於多個指標指向同一個物件的情形。
10.3 weak_ptr
#include <iostream>
using namespace std;
class Son;
class Father;
class Father
{
public:
shared_ptr<Son> son;
~Father()
{
cout << "~Father() " << endl;
}
};
class Son
{
public:
shared_ptr<Father> father;
~Son()
{
cout << "~Son()"<< endl;
}
};
void CrossReference(void);
int main()
{
cout<<"start of main..."<<endl;
CrossReference();
cout << "end of main..." << endl;
return 0;
}
void CrossReference(void)
{
shared_ptr<Father> f(new Father);
shared_ptr<Son> s(new Son);
// 交叉引用
f->son = s;
s->father = f;
cout << "類Father的引用計數:" << f.use_count() << endl;
cout << "類Son的引用計數:" << s.use_count() << endl;
return;
}
執行結果:
start of main...
類Father的引用計數:2
類Son的引用計數:2
end of main...
為什麼沒有呼叫類Father和類Son的解構函式?
在這之前,我們先了解什麼是交叉引用。類A中對類B進行引用,則A物件要銷燬則必須先銷燬B物件;類B中對類A進行引用,則B物件要銷燬則必須先銷燬A物件。這樣就造成了一個死鎖,誰都銷燬不了。類A中對類B進行引用,物件A可以決定物件B的生死,稱之為強指標或強引用;A中對類B進行引用,物件A決定不了物件B的生死,稱之為弱指標或弱引用。
對於上面程式碼中的交叉引用,可以用弱指標來解決。在C++11庫中提供了弱指標weak_ptr。它需要包含標頭檔案memory(被標頭檔案iostream包含了)和使用名稱空間std。
#include <iostream>
using namespace std;
class Son;
class Father;
class Father
{
public:
weak_ptr<Son> son;
~Father()
{
cout << "~Father() " << endl;
}
};
class Son
{
public:
shared_ptr<Father> father;
~Son()
{
cout << "~Son()"<< endl;
}
};
void CrossReference(void);
int main()
{
cout<<"start of main..."<<endl;
CrossReference();
cout << "end of main..." << endl;
return 0;
}
void CrossReference(void)
{
shared_ptr<Father> f(new Father);
shared_ptr<Son> s(new Son);
// 交叉引用
f->son = s;
s->father = f;
cout << "類Father的引用計數:" << f.use_count() << endl;
cout << "類Son的引用計數:" << s.use_count() << endl;
return;
}
執行結果:
start of main...
類Father的引用計數:2
類Son的引用計數:1
~Son()
~Father()
end of main...
只要交叉引用的其中一邊使用weak_ptr就可以解開死鎖。
在安卓的原始碼中,提供了輕量級指標、強指標、弱指標對C++的物件進行回收,有興趣的可以研究下它的原理。
https://blog.csdn.net/kc58236582/article/details/52279346這篇部落格寫的不錯,大家可以看看。