智能指針
1.基礎:
1.1 概念
智能指針是用于自動管理動態分配內存的RAII(Resource Acquisition Is Initialization)對象。它們通過自動釋放內存來防止內存泄漏,是替代裸指針的安全工具。
1.2 解析
眾所周知,堆內存對象 需要 手動 使用 delete 銷毀 , 如果 沒有 使用 delete 銷毀 就會 造成 內存 泄漏 。所以C + + 在ISO9 8 標準 中 引入了 智能 指針 的 概念 , 并 在 I S O 1 1 中 趨于 完善 。用智能指針可以讓堆內存對象具有棧內存對象的特點,原理是給需要手動回收的內內存對象套上一個棧內存的模板類對象的即可。
如下圖,智能指針類似把堆區指針對象放到新的指針類中進行管理,這樣就可以和棧區對象一樣,所處的{}結束就會自動釋放內存,不用手動delete。
2.分類:
自動指針:auto_ptr? ? ? ? (C++ISO98,已被廢除)
唯一指針:unique_ptr? ? (C++IOS11)
共享指針:shared_ptr? ? (C++IOS11)
虛指針:weak_ptr? ? ? ? ? (C++IOS11)
2.1 自動指針
(1)使用:
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":構造函數" << endl;}~Animal(){cout << breed << ":析構函數" << endl;}void print(){cout << "物種:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){{Animal *a = new Animal("狗","mike");auto_ptr<Animal> ap1(a); //自動指針管理堆區指針對象aap1.get()->print(); //使用get函數調用管理的指針對象//ap1.release(); //直接放棄對已有指針對象a的管理,不會釋放堆區空間,如果不delete對象a可能導致內存泄露//ap1.reset(); //放棄對已有指針對象a的管理,同時釋放堆區空間/**1. 放棄對原有指針的管理,同時釋放空間,*2. 再創建新的堆區對象*3. ap1指針對新的堆區指針對象進行管理*/ap1.reset(new Animal("貓","lisa"));ap1.get()->print(); //打印新管理的對象的成員變量cout << "程序塊運行結束" << endl;}cout << "程序運行結束" << endl;return 0;
}
(2)問題(被廢除原因):
由于成員變量存在指針類型,因此拷貝構造函數與賦值運算符重載的使用會出現問題,與淺拷貝不同的是,auto_ptr的復制語義會造成資源控制權轉移的問題。
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":構造函數" << endl;}~Animal(){cout << breed << ":析構函數" << endl;}void print(){cout << "物種:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){{Animal *a = new Animal("狗","mike");auto_ptr<Animal> ap1(a); //自動指針管理堆區指針對象aauto_ptr<Animal> ap2(ap1); //顯示調用拷貝函數cout << ap1.get() << " " << ap2.get() << endl;auto_ptr<Animal> ap3 = ap2; //隱式調用拷貝函數cout << ap1.get() << " " << ap2.get() << " " << ap3.get() << endl;auto_ptr<Animal> ap4;ap4 = ap3; //調用的是重載的賦值運算符cout << ap1.get() << " " << ap2.get() << " " << ap3.get() << " " << ap4.get() << endl;cout << "程序塊運行結束" << endl;}cout << "程序運行結束" << endl;return 0;
}
2.2 唯一指針
(1)特點:
1.和auto_ptr基本一致,唯一的不同就是unique_ptr指針對資源對象有唯一控制權,不能使用常規語法直接賦值和調用拷貝構造函數拷貝資源對象的控制權。
2.如果想讓別的unique_ptr指針搶奪資源控制權就使用move(ap1)。
(2)使用:
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":構造函數" << endl;}~Animal(){cout << breed << ":析構函數" << endl;}void print(){cout << "物種:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){{Animal *a = new Animal("狗","mike");unique_ptr<Animal> up1(a); //自動指針管理堆區指針對象aup1.get()->print();//unique_ptr<Animal> up2(up1); //不使用move不能轉移控制權,下面兩種方式一樣unique_ptr<Animal> up2(move(up1)); //顯示調用拷貝函數cout << up1.get() << " " << up2.get() << endl;unique_ptr<Animal> up3 = move(up2); //隱式調用拷貝函數cout << up1.get() << " " << up2.get() << " " << up3.get() << endl;unique_ptr<Animal> up4;up4 = move(up3); //調用的是重載的賦值運算符cout << up1.get() << " " << up2.get() << " " << up3.get() << " " << up4.get() << endl;cout << "程序塊運行結束" << endl;}cout << "程序運行結束" << endl;return 0;
}
2.3 共享指針
(1)特點:
1.對一個堆區對象進行管理,解決了搶奪控制權轉換的問題,可以多個共享指針一起管理一個堆區對象。
2.新增一個引用計數,對一起管理同一個對象的指針計數,每多一個共享指針管理這個堆區對象,引用計數加一。
3.當管理這個堆區空間的所有共享指針都被釋放,才會回收這個堆區對象。?
4.shared_ptr有兩種創建方式,一種和之前的創建方式一樣。第二種常用,是其他智能指針沒有的,使用:shared_ptr<類型> sp1 = make_shared<類型>(初始化內容)。
(2)使用:
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":構造函數" << endl;}~Animal(){cout << breed << ":析構函數" << endl;}void print(){cout << "物種:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){shared_ptr<Animal> sp3;{//shared_ptr<Animal> sp1(new Animal("狗","mike")); //先創建堆區對象,再讓智能 指針引用管理堆區對象,速度慢shared_ptr<Animal> sp1 = make_shared<Animal>("狗","mike"); //直接創建智能指針并且引用對象,一步到位sp1.get()->print();cout << sp1.get() << endl;cout << "引用計數:" << sp1.use_count() << endl;shared_ptr<Animal> sp2 = sp1; //隱式調用拷貝構造函數cout << sp1.get() << " " << sp2.get() << endl;cout << "引用計數:" << sp1.use_count() << endl;sp3 = sp2; //賦值運算符重載cout << sp1.get() << " " << sp2.get() << " " << sp3.get() << endl;cout << "引用計數:" << sp1.use_count() << endl;cout << "程序塊運行結束" << endl;}cout << "引用計數:" << sp3.use_count() << endl; //程序塊結束,只剩下ap3管理對象cout << "程序運行結束" << endl;return 0;
}
2.4 虛指針
虛指針weak_ptr是用來觀查共享指針所管理的資源,像一個旁觀者,來記錄觀查的對象(堆區對象)是否被shared_ptr管理,有幾個share_ptr在管理。
(1)特點:
1.虛指針weak_ptr是一個不控制資源對象管理的智能指針,不會影響資源的引用計數,主要的目的是協助share_ptr工作
2.通過weak_ptr的構造函數,參數傳入一個持有資源對象的share_ptr或者weak_ptr的指針
3.weak_ptr與資源呈弱相關性,不能單獨對資源進行控制,不可以調用get()函數操作資源
4.weak_ptr可以調用.lock函數讓shared_ptr指針獲得一個持有資源的share_ptr,在調用.lock函數前要先檢測weak_ptr的引用計數是否大于零(觀察的資源是否還有shared_ptr指針還在管理,如果沒有,對應的資源也不存在)。
#include <iostream>
#include <memory>using namespace std;class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":構造函數" << endl;}~Animal(){cout << breed << ":析構函數" << endl;}void print(){cout << "物種:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){weak_ptr<Animal> wp2;shared_ptr<Animal> sp3;{shared_ptr<Animal> sp1 = make_shared<Animal>("狗","mike"); //直接創建智能指針并且引用對象,一步到位sp1.get()->print(); //打印weak_ptr<Animal> wp1(sp1); //使用虛函數wp1來觀測共享指針對堆區對象引用次數,不會增加引用計數cout << "引用計數:" << sp1.use_count() << endl;cout << "引用計數:" << wp1.use_count() << endl;shared_ptr<Animal> sp2 = sp1; //隱式調用拷貝構造函數//sp1.reset(); //刪除當前共享指針,如果還有其他共享指針管理就不釋放所管理的資源wp2 = sp2; //wp2也觀測堆區對象cout << "引用計數:" << sp1.use_count() << " " << wp2.use_count() << endl;sp3 = sp2;cout << "程序塊運行結束" << endl;}//即使觀測的共享指針被釋放,wp2依舊存在,wp1被釋放cout << "引用計數:" << wp2.use_count() << endl; //程序塊結束,虛函數觀測管理資源對象的引用計數--0cout << "程序運行結束" << endl;return 0;
}
3.手寫一個共享指針Share_ptr類
- 構造函數
- 拷貝構造函數
- 賦值運算符
- get函數
- use_count函數
- reset函數
- 析構函數
代碼實現:
#include <iostream>
#include <memory>using namespace std;template <class T>
class Shareptr{
private:T *res = nullptr;int *count = nullptr;
public:Shareptr(){}Shareptr(T *s):res(s),count(new int(1)){} //構造函數Shareptr(const Shareptr &sp):res(sp.res),count(sp.count){ //拷貝構造函數(*count)++;}T* get()const{ //調用管理的資源return res;}int use_count()const{return *count;}void reset(){ //刪除當前共享指針,如果還有其他共享指針管理資源對象,不刪除資源對象if(count != nullptr && res != nullptr){ //如果當前共享指針不是空(*count)--;if((*count) == 0){delete res;delete count;}res = nullptr;count = nullptr;}}Shareptr &operator =(const Shareptr &sp){ //賦值運算符重載if(&sp != this){reset();res = sp.res;count = sp.count;(*count)++;}return *this;}~Shareptr(){reset();}
};class Animal{
private:string breed;string name;
public:Animal(string breed,string name):breed(breed),name(name){cout << this->breed << ":構造函數" << endl;}~Animal(){cout << breed << ":析構函數" << endl;}void print(){cout << "物種:" << breed << endl;cout << "名字:" << name << endl;}
};int main (){Shareptr<Animal> sp3;{//Shareptr<Animal> sp1 = make_shared<Animal>("金漸層","lisa");//Shareptr<Animal> sp1 = new Animal("金漸層","lisa"); //隱式調用構造函數Shareptr<Animal> sp1(new Animal("金漸層","lisa")); //顯示調用構造函數sp1.get()->print();cout << sp1.use_count() << " " << sp1.get() << endl;Shareptr<Animal> sp2 = sp1; //調用拷貝構造函數cout << sp1.use_count() << " " << sp1.get() << endl;cout << sp2.use_count() << " " << sp2.get() << endl;sp3 = sp1; //調用重載的賦值運算符cout << sp1.use_count() << " " << sp1.get() << endl;cout << sp2.use_count() << " " << sp2.get() << endl;cout << sp3.use_count() << " " << sp3.get() << endl;}cout << "程序結束" << endl;return 0;
}
擴展:shared_ptr 可能產生的內存泄露
問題描述:
當兩個或多個對象通過?shared_ptr?互相持有對方時,會形成循環引用(也叫“環狀引用”)。這樣即使它們都不再被外部引用,由于它們內部的?shared_ptr?還在互相引用,引用計數永遠不會歸零,導致內存無法釋放,產生內存泄漏。
錯誤示例:
#include <iostream>
#include <memory>
using namespace std;class Car; // 前向聲明class Person {
public:shared_ptr<Car> car;~Person() { cout << "Person destroyed" << endl; }
};class Car {
public:shared_ptr<Person> owner;~Car() { cout << "Car destroyed" << endl; }
};int main() {auto p = make_shared<Person>();auto c = make_shared<Car>();p->car = c;c->owner = p;// main 結束后,Person 和 Car 都不會被銷毀
}
運行后不會輸出?A destroyed?和?B destroyed,因為它們的引用計數始終大于0。
解決辦法
用?weak_ptr?打破循環引用。
weak_ptr?是一種不增加引用計數的智能指針。通常在一方用?shared_ptr,另一方用?weak_ptr。
修改后:
#include <iostream>
#include <memory>
using namespace std;class Car; // 前向聲明class Person {
public:shared_ptr<Car> car;~Person() { cout << "Person destroyed" << endl; }
};class Car {
public:weak_ptr<Person> owner; // 用 weak_ptr~Car() { cout << "Car destroyed" << endl; }
};int main() {auto p = make_shared<Person>();auto c = make_shared<Car>();p->car = c;c->owner = p; // 這里是 weak_ptr,不增加引用計數// main 結束后,Person 和 Car 都會被正確銷毀
}
這樣,A?和?B?都會被正確釋放,輸出:
總結
循環引用會導致?shared_ptr?管理的對象無法釋放,造成內存泄漏。
用?weak_ptr?替代其中一方的?shared_ptr,即可打破循環,避免泄漏。