智能指针1.0
一.使用普通的動態內存開辟存在的問題
我們在使用動態內存開辟一個空間的時候,需要釋放掉這個空間,不然就容易出現內存泄漏。
比如下面的程序
情況一:
#include<iostream>
using namespace std;
int errorTest()
{
???????? intflag = 0;
???????? cin>> flag;
???????? int*p = new int;
???????? if(flag)
???????? {
?????????????????? cout<< "success" << endl;
?????????????????? return0;
???????? }
???????? else
???????? {
?????????????????? deletep;
?????????????????? cout<< "error" << endl;
?????????????????? return1;
???????? }
}
如果我們的程序是正確的條件,那么這個時候直接是返回的,此時一種情況就是我們動態開辟的空間沒有釋放掉
情況二:
void errorTest2()
{
???????? int*p;
???????? try
???????? {
?????????????????? p= (int*)malloc(1000);
???????? }
???????? catch(...)?? //如果捕獲到了一個異常,程序就會跳轉了,然后就沒有下面的delete了
???????? {
?????????????????? throw;
???????? }
???????? deletep;
}
如果我們捕獲了一個異常,那么這個時候我們的程序就直接跳轉了,下面的代碼是不執行的,這個時候我們的資源也沒有釋放掉
?
有的同學可能就會說了,這里我開辟的資源少,對我的系統影響很少的,還有的同學可能也會給出另外的一種解釋就是,我們開辟的資源是在堆上面開辟的,當我們的進程退出之后,我們的資源就釋放了,但是如果是這樣的一種情況呢,我們的服務器是7*24小時一直運行著的,而且我們的這一段代碼可能還是循環執行著的,這樣一點點占據資源,我們的內存遲早有點會被吃完,然后我們的服務器就崩潰掉了。
二.解決辦法
我們想到C++類中有一些函數是自動的調用的,就是構造函數和析構函數,我們想利用這個來進行動態內存的管理,就是讓一個動態空間在使用結束后就直接釋放掉了。
這里首先想到的就是一個定義一個類,用這個類實例化的對象來維護我們的動態空間。
這里我們要用到一種技術是RAII技術,就是資源獲得即初始化
管理權的轉移
auto_ptr
這里首先提出的一個方法是auto_ptr,下面我們就來模擬實現這個類
先看一個簡單的代碼
template<class T>
class AutoPtr
{
public:
???????? AutoPtr(T*ptr)
?????????????????? :_ptr(ptr)
???????? {}
???????? ~AutoPtr()
???????? {
?????????????????? delete_ptr;
???????? }
???????? //這里應該返回的是一個對象,這里T就是一個對象,然后我們需要改變里面的內容,所以這里返回的應該是T&
???????? T&operator*()
???????? {
?????????????????? return*_ptr;
???????? }
???????? T*operator->()?? //這里相當于是放回了一個指針,然后在使用這個指針指向一個內容
???????? {
?????????????????? return_ptr;
???????? }
private:
???????? T*_ptr;
};
下面我們還需要實現的一個內容是拷貝構造函數還有賦值運算符的重載
拷貝構造函數的時候就不得不想的一個問題就是,如果我們多個指針維護一個空間的話,當一個指針結束后,釋放了,那么當其他的指針結束之后再次釋放,程序就會崩潰掉了,因為之前的內容已經釋放掉了
我們首先想到的方法是深拷貝,就是每次拷貝的時候都給這個指針開辟一個空間,但是這種設計是不符合我們的使用要求,我們既然要求它像指針一樣使用,那么當我們的一個指針使用*p改變空間內容的時候只有當前的內容改變了,但是其他的內容沒有改變,所以這種是不符合我們的要求的
這個時候我們想解決的辦法就是在設計這個類的時候我們可以多設計一個BOOL型變量,對于動態空間的釋放權只能 有一個指針有權限管理,其他的指針沒有這個權限,而且我們規定只有最后一次拷貝構造的那個指針有這個權限去釋放指針。
按照這個思路我們的拷貝構造函數還有我們的賦值運算符的重載應該設計成下面的形式
#include<iostream>
using namespace std;
struct A
{
???????? inta;
???????? intb;
};
template<class T>
class AutoPtr
{
public:
???????? AutoPtr(T*ptr = NULL)
?????????????????? :_ptr(ptr)
?????????????????? ,_owner(true)
???????? {}
???????? ~AutoPtr()
???????? {
?????????????????? if(_owner == true)
?????????????????? delete_ptr;
???????? }
???????? //這里應該返回的是一個對象,這里T就是一個對象,然后我們需要改變里面的內容,所以這里返回的應該是T&
???????? T&operator*()
???????? {
?????????????????? return*_ptr;
???????? }
???????? T*operator->()?? //這里相當于是放回了一個指針,然后在使用這個指針指向一個內容
???????? {
?????????????????? return_ptr;
???????? }
???????? AutoPtr(AutoPtr<T>&p)
?????????????????? :_ptr(p._ptr)
?????????????????? ,_owner(p._owner)
???????? {
?????????????????? p._owner= false;
???????? }
???????? AutoPtroperator=(AutoPtr<T>& p)
???????? {
?????????????????? if(&p == this)
?????????????????? {
??????????????????????????? return*this;
?????????????????? }
?????????????????? else
?????????????????? {
??????????????????????????? if(_owner = true)
??????????????????????????? {
???????????????????????????????????? delete_ptr;
??????????????????????????? }
?????????????????? ???????? _ptr = p._ptr;
??????????????????????????? _owner= p._owner;
??????????????????????????? p._owner= false;
??????????????????????????? return*this;
?????????????????? }
???????? }
private:
???????? T*_ptr;
???????? bool_owner;
};
void AutoTest()
{
???????? AutoPtr<int>p(new int);
???????? *p= 10;
???????? cout<< *p << endl;
???????? AutoPtr<int>p2(p);
???????? *p2= 20;
???????? cout<< *p2 << endl;
???????? AutoPtr<A>pa(new A);
???????? AutoPtr<int>p3;
???????? p3= p2;
???????? *p3= 30;
???????? cout<< *p3;
???????? pa->a;?? //這里實際上是這個樣子的pa.operator*()->a;
}
auto_ptr問題分析
管理權轉移了,但是我們還存在著另外的一個問題就是,如果在一個if語句中,我們把使用了拷貝構造函數構造一個p3,這個時候管理權在p3中,因為某些原因p3釋放了這個空間,但是我們在if的外面還想使用cout<<*p2<<endl;這個時候是不是就出現了問題了呢
第二種方式當我們使用拷貝構造函數的時候,比如我們使用了p2(p1),這個時候直接使p1 = NULL
scoped_ptr
為了防止我們的程序出現那樣的問題,我們的標準模板庫又設計了一種簡單粗暴的方式就是防拷貝,這個時候就是只能一個指針管理一個存儲空間
代碼實現如下(其中只實現了拷貝構造函數部分)
#include<iostream>
using namespace std;
template<class T>
class ScopedPtr
{
public:
???????? ScopedPtr(T*ptr = NULL)
?????????????????? :_ptr(ptr)
???????? {}
???????? ~ScopedPtr()
???????? {
?????????????????? delete_ptr;
???????? }
private:
???????? ScopedPtr(ScopedPtr&p);?? //這里采用的是只聲明不定義的方式,那么這個如果我們在使用的時候,編譯的時候就會出錯
???????? //這里我們還應該注意的一點就是,我們的拷貝構造函數要放置在private中,如果放置在公有中,這個時候是很有可能被攻擊
???????? //如果被攻擊了,然后別人寫了一個函數,對我們的程序不力,這個時候就容易造成麻煩
?
private:
???????? T*_ptr;
};
?
void ScopedPtrTest()
{
???????? ScopedPtr<int>p(new int);
???????? ScopedPtr<int>p1(p);
????????
????????
}
?
總結
- 上一篇: 模板的分离编译
- 下一篇: 简易git操作 -- 让你的格子绿起来