1. 程式人生 > >C++進階(語法篇)—第10章 智慧指標

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

 * iptr = new int(a);

cout << "*iptr = " << *iptr << endl;

return;

}

當Smart_pointer函式執行完後,由於沒有delete iptr(忘了寫),因此iptr被銷燬,而iptr指向的那塊堆記憶體依舊存在,造成了記憶體洩漏。

注:當main函式執行完後,系統會自動清理堆空間;但是有的程式main函式會一直執行。

#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 * iptr = new int(a);

cout << "*iptr = " << *iptr

<< endl;

//...發生異常...

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這篇部落格寫的不錯,大家可以看看。