管理pointer很難
我們需要保證當許多個指針指向同一個對象的時候,我們怎麼去管理這些指針,並且不希望出現空懸指針和多次析構一個指針的情況出現。通常爲了解決以上的尷尬,我們使用智能指針來實現對於pointer的管理。
smart pointer可以實現知道自己什麼時候應該釋放自己的內存,而不至於影響到其他場合對於塊內存的使用。但是在使用智能指針的時候我們仍需要考慮到其特點不然也會爲其的智能化而付出一點代價。
c++標準庫設計智能指針的思路
- 共享類型的指針:實現資源共有的概念,多個指針可以指向同一個對象,該對象和其相關資源會在最後一個對於該對象的引用被銷燬的時候析釋放內存空間。有一些輔助類可以使用weak_ptr,bad_weak_ptr,enable_share_from_this等等。
- 獨佔類型的指針:實現獨佔式的擁有,或者說是嚴格擁有該指針的資源,不允許其他的智能指針共享,在生命週期內只允許一個智能指針擁有資源。但是可以移交所有權,用於避免內存泄漏的產生。比如new之後忘記delete這種情況就可以被好好地解決。
shared_ptr的內部結構
template<class T>
class shared_ptr{
public:
T& operator*() const{
return *px;
}
T* operator->() const
{return px;}
private:
T * px;
long * pn;
...
};
我麼在使用的時候直接將其按照普通指針的方式來使用,一個普通的指針是用來實現一些比如說* ->的運算的,爲了使得這些智能指針調用函數的時候,調用的是內部的指針包含的元素的函數,我們需要了解->的特性,其實就是會循環調用到底所有的引用,第一次的引用會調用return一個內部指針出來,之後還會找到這個指針內部的函數。
其實這種設計在迭代器裏面也被使用到,而且基本是一些有++ -- 操作的容器的迭代器都會重載這些運算符,所以有時候稱迭代器的功能也是類似智能指針的。
使用share_ptr
在使用share_ptr的時候其實和指針的使用方法差不多,因爲實現了許多指針的運算符的重載,但是智能指針也有許多獨特的用法。
下面舉一個使用的例子
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;
int main()
{
//初始化智能指針,將指針放進share_ptr保存,使用新式初始化OK允許
shared_ptr<string> pExample(new string("just example"));
shared_ptr<string> pExample2(new string("example 2"));
//需要注意的點就是,shared_ptr的構造函數裏面參數爲單個指針的函數爲expilict
//所以不允許使用賦值,其實也就是不允許隱式構造
//使用方式和普通string *都是一樣的
(*pExample)[0] = 'N';
pExample2->replace(0,1,"E");
//share_ptr是可以用在容器裏面進行使用的,因爲share_ptr是允許進行復制和移動的
vector< shared_ptr<string>> MyExamples;
MyExamples.push_back(pExample);
MyExamples.push_back(pExample2);
//雖然裏面都是一樣的share_ptr但是其實他們都是一個內存空間的引用
//只要一個元素修改了內容就會導致所有的內容都會被修改
MyExamples.push_back(pExample);
MyExamples.push_back(pExample2);
for(auto i : MyExamples)
{
cout << *i << endl;
}
}
輸出結果
分析一下上面的例子:
首先關於shared_ptr的初始化
//初始化智能指針,將指針放進share_ptr保存,使用新式初始化OK允許
shared_ptr<string> pExample(new string("just example"));
shared_ptr<string> pExample2(new string("example 2"));
//需要注意的點就是,shared_ptr的構造函數裏面參數爲單個指針的函數爲expilict
//所以不允許使用賦值,其實也就是不允許隱式構造
//同時也可以使用make_shared()便捷函數
shared_ptr<string> pExam = make_shared<string>("hhhh");
//想要給share指針賦值,不能直接賦值,但是可以使用reset函數
pExample.reset(new string("also example"));
可以自己設計share_ptr的deleter,這個deleter可以由自己設計,引用計數變爲0的時候就可以調用這個自己設計的deleter。智能指針的默認析構函數調用的是delete而已,並不是delete[ ] ,所以也只是適用於單一對象存儲在shared_ptr裏面的析構情況。
//初始化的時候就把需要的析構函數傳進去
shared_ptr<int> p(new int[10],[](int *){
delete[] p;
})
//或者使用
std::shared_ptr<int> p(new int[10],std::default_delete<int[]>());
shared_ptr與unique_ptr在這上面有點區別
//對於一個unique_ptr來說可以直接指定類型就可以使用,但是對於share_ptr是不允許這樣的
std::unique_ptr<int[]> p(new int[10]);
std::shared_ptr<int[]> p(new int[10]); //編譯不過去。。。
std::unique_ptr<int,void(*)(int *)> p(new int[10],[](int *p){
delete [] p ;
});
shared_ptr不提供operator [ ] ,至於unique_ptr它有一個針對於array的偏特化版本,該版本提供了operator[]。取代operator*和operator->