动态内存管理和智能指针 2.0 -- shared_ptr
shared_ptr出現原因
通過第一章的學習,我們知道不管是auto_ptr合適scoped_ptr都是存在缺陷的,于是我們必須想出一個方法既能很好的管理我們的內存,而且在使用的時候,可以多個指針指向一個內存,這個時候就出現了shared_ptr。
shared_ptr的實現原理
shared_ptr使用的引用計數的淺拷貝的形式,這個時候是不需要使用引用計數的寫時拷貝的,因為多個指針指向的是同一個動態的內存空間,當其中的一個內存空間改變的時候,其他的內容也是相應的改變的。
這個時候我們的shared_ptr這個類的成員變量中就要有一個指針,用于指向一個動態開辟的存儲空間,還需要有一個用于計數的指針,這個指針指向一個動態開辟的內存空間,一般是整型的,這個整型 變量中存放的是我們指向同一個空間的個數,然后這個動態的整型空間只在構造函數的使用開辟出來,其他的拷貝構造函數還有賦值運算符的重載 的時候直接的使用了。
簡單代碼實現
template<class T>
class Shared_Ptr
{
public:Shared_Ptr(T* ptr):_ptr(ptr),_count(new int(1)){}Shared_Ptr(const Shared_Ptr& ptr):_ptr(ptr._ptr), _count(ptr._count){(*_count)++;}Shared_Ptr& operator=(const Shared_Ptr& ptr){if (this == &ptr){return *this;}else{if (!--(*_count)){delete _ptr;delete _count;cout << "delete older" << endl;}_ptr = ptr._ptr;_count = ptr._count;(*_count)++;}return *this;}~Shared_Ptr(){(*_count)--;if ((*_count) == 0){delete _ptr;delete _count;cout << "delete" << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _count;
};
代碼分析
構造函數
當我們使用構造函數的時候,這個時候肯定是已經動態開辟了一個內存空間的,所以我們這個時候也給我們_count指針動態的開辟一個空間,并且這個空間的值是1,因為此時一定是有一個空間了。
拷貝構造函數
拷貝構造函數的時候,我們是讓一個已經存在的對象去初始化另一個對象,所以這個時候我們只需要讓當前對象的指針指向那個動態的空間,同時時當前對象的計數指針也指向那個對象的計數空間,并且使當前對象的計數值加1,因為這個時候已經有兩個對象 指向了一個動態的空間了。
析構函數
析構函數的時候,我們需要把引用計數減一,這個時候再去判斷我們的引用計數的值是否為0,如果是0,這個時候就需要釋放我們的動態管理的空間,同時釋放掉我們的引用計數的動態空間,同時需要把這兩個指針置為NULL。
賦值運算符的重載
該檢查的是,這個賦值是不是自賦值,如果是 自賦值,這個時候直接返回該對象 即可了,還需要注意的是,我們 的引用計數不需要加1.如果不是自賦值,此時需要把當前對象的引用計數減1,同時判斷減1之后應用計數是不是為0,如果是0則需要釋放掉,如果不是0,就不要管了,然后接下來把我們當前指針指向被賦值的那個空間即可。
循環引用和weak_ptr
當下面的代碼的時候會出現一種情況就是 循環引用,首先看代碼,下面的代碼是我們定義的一個結構體
struct Str
{Shared_Ptr<Str> _prev;Shared_Ptr<Str> _next;
};
再來看我們的構造函數
Shared_Ptr(T* ptr):_ptr(ptr),_count(new int(1)){}
上面的構造函數中,我們的構造函數沒有缺省值,會報錯,因為我們在定義_prev和_next的時候,沒有傳入參數,所以我們需要把我們的構造函數改成下面的樣子,就是把缺省值賦值為NULL。
這個時候我們寫一個測試用例
Shared_Ptr<Str> a = new Str;
Shared_Ptr<Str> b = new Str;
這個時候會打印出六個delete,因為我們new出來的兩個對象本身是有兩個對象的,所以析構的時候,會析構這里面的兩個一個是_next一個是_prev,然后我們的a和b本身也是指向對象的,所以一共析構了六次。
隱患問題 – 循環引用
如果我們是像下面的方式 使用它,就會出現循環 引用的問題,請看下面的代碼
Shared_Ptr<Str> a = new Str;Shared_Ptr<Str> b = new Str;a->_next = b;b->_prev = a;
結合剛剛的分析,我們來分析上面的一段程序,首先是a和b分別指向了兩個new出來的對象 ,然后這兩個對象 里面的next和prev分別指向了兩個對象,接著 執行a->_next = b;
b->_prev = a;的時候,會調用賦值運算符的重載,然后就是a里面的next由原來的內容指向了b,b里面 的prev由原來的內容指向了a;這個時候問題就來了,b這個指向指向的內存空間有兩個指針指向著,一個是b自己,一個是a->_next,所以析構的時候不會釋放內存空間,這不是我們想看到的結果。也可以這樣子分析,就是我們的a和b析構的時候,只是 把引用計數減1,接下來析構a->_next和b->prev的時候,都是相互依賴彼此的,所以都釋放不了,這就是循環引用。
weak_ptr
于是為了解決上面的循環引用的特殊場景,配合著shared_ptr設計出了一個weak_ptr,代碼如下
#include<iostream>
using namespace std;template<class T>
class Weak_ptr;template<class T>
class Shared_Ptr
{public:friend class Weak_ptr<T>;Shared_Ptr(T* ptr = NULL):_ptr(ptr),_count(new int(1)){}Shared_Ptr(const Shared_Ptr& ptr):_ptr(ptr._ptr), _count(ptr._count){(*_count)++;}Shared_Ptr& operator=(Shared_Ptr& ptr){if (this == &ptr){return *this;}else{if (!--(*_count)){delete _ptr;delete _count;cout << "delete older" << endl;}_ptr = ptr._ptr;_count = ptr._count;(*_count)++;}return *this;}~Shared_Ptr(){(*_count)--;if ((*_count) == 0){delete _ptr;delete _count;_ptr = NULL;_count = NULL;cout << "delete" << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}protected:T* _ptr;int* _count;
};template<class T>
class Weak_ptr
{
public:Weak_ptr():_ptr(NULL){}Weak_ptr(Shared_Ptr<T> ptr){_ptr = ptr._ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};struct Str
{Weak_ptr<Str> _prev;Weak_ptr<Str> _next;int _a;
};void TestPtr()
{Shared_Ptr<Str> a = new Str;Shared_Ptr<Str> b = new Str;a->_next = b;b->_prev = a;a->_next->_a = 0;cout << b->_a;}
首先我們看Weak_ptr,它的成員變量是T* _ptr;此時我們的Str那個自定義的結構體中的指針就可以改成了Weak_ptr的形式,因為我們的Weak_ptr維護的是一個普通的指針,但是我們在使用的時候需要用到_next指向一個Shared_ptr的對象,所以這個時候,我們不要把Weak_ptr的拷貝構造函數的參數寫成是Shared_ptr,然后賦值的時候,需要把Shared_ptr的_ptr賦值給Weak_ptr的_ptr,但是Shared_ptr中的_ptr是 私有的,所以這個時候我們在Shared_ptr里面把Weak_ptr聲明為友元。但是這個時候又出現了一個 問題就是,我們的他聲明為友元之后,編譯器找不到我們的友元類,因為我們的Weak_ptr的定義部分是在Shared_ptr的后面,所以這個時候,需要在Shared_ptr的前面聲明我們的Weak_ptr。
定制防函數
所謂的防函數就是讓我們的類看起來像是函數一樣
舉一個簡單的例子,看下面的代碼
struct Compare
{bool operator()(int a,int b){return a > b;}
};void test()
{Compare com;cout << com(1,2);
}
類Compare是我們定制的一個防函數,下面的test就是我們把他 當成一個函數來使用它
為什么要引入防函數呢,因為我們在使用的時候上面的Shared_ptr的時候,我們管理的內存 空間可能是一個FILE*的一個指針,這個時候我們就不能只使用delete來釋放我們的空間,這個時候我們就需要定制一個防函數,把它作為一個參數放在構造函數 中,同時我們的Shared_ptr的成員變量里面需要定義一個這樣的變量。
請看下面的代碼
#include<iostream>
using namespace std;
#include<assert.h>
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1struct DelFile
{void operator()(FILE* f){fclose(f);cout << "fclose" << endl;}
};struct DelDel
{void operator()(void* p){assert(p);delete p;cout << "delete" << endl;}
};template<class T,class Del>
class Weak_ptr;template<class T,class Del>
class Shared_Ptr
{public:friend class Weak_ptr<T,Del>;Shared_Ptr(T* ptr,Del d):_ptr(ptr),_count(new int(1)), _del(d){}Shared_Ptr(const Shared_Ptr& ptr):_ptr(ptr._ptr), _count(ptr._count){(*_count)++;}Shared_Ptr& operator=(Shared_Ptr& ptr){if (this == &ptr){return *this;}else{if (!--(*_count)){del(_ptr);delete _count;cout << "delete older" << endl;}_ptr = ptr._ptr;_count = ptr._count;(*_count)++;}return *this;}~Shared_Ptr(){(*_count)--;if ((*_count) == 0){_del(_ptr);delete _count;_ptr = NULL;_count = NULL;//cout << "delete" << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}protected:T* _ptr;int* _count;Del _del;
};template<class T, class Del>
class Weak_ptr
{
public:Weak_ptr():_ptr(NULL){}Weak_ptr(Shared_Ptr<T,Del> ptr){_ptr = ptr._ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};struct Str
{Weak_ptr<Str,DelDel> _prev;Weak_ptr<Str, DelDel> _next;int _a;
};void TestFile()
{DelFile d;Shared_Ptr<FILE, DelFile> a(fopen("w.ss","w"),d); //注意這里傳參的時候,首先要實例化一個對象//我一開始使用的是Shared_Ptr<FILE, DelFile> a(fopen("w.ss","w"),DelFile d)//這種方式顯然是錯誤的,我不能在一個函數里面去實例化一個對象
}void TestPtr()
{DelDel d;Shared_Ptr<Str, DelDel> a(new Str, d);Shared_Ptr<Str, DelDel> b(new Str, d);a->_next = b;b->_prev = a;a->_next->_a = 0;cout << b->_a;}struct Compare
{bool operator()(int a,int b){return a > b;}
};void test()
{Compare com;cout << com(1,2);
}
總結
以上是生活随笔為你收集整理的动态内存管理和智能指针 2.0 -- shared_ptr的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简易git操作 -- 让你的格子绿起来
- 下一篇: 梦幻西游中12345J的玛瑙和黑宝石分别