一、auto_ptr的簡單模擬實現
關於auto_ptr的缺陷我在之前的博客中就已經說明。在這篇博客中我們從它的底層來更進一步闡述。
在學習中,對某一個類進行簡單的模擬,一般情況下,我們只需要模擬實現四個函數,分別是:構造函數、析構函數、拷貝構造函數以及賦值運算符重載。但是對於一個指針來說,它還要加上*
運算符重載和->
運算符重載
template<class T>
class Auto_ptr
{
public:
//構造函數
Auto_ptr(T* _ptr=NULL)
{
ptr = _ptr;
}
//析構函數
~Auto_ptr()
{
if (ptr != NULL)
{
delete ptr;
}
}
//拷貝構造函數
Auto_ptr(const Auto_ptr<T>& ap)
{
ptr = ap.ptr;
ap.ptr = NULL;
}
//賦值運算符重載
Auto_ptr<T>& operator=(Auto_ptr<T>& ap)
{
if (this != &ap)
{
if (ptr != NULL)
delete ptr;
ptr = ap.ptr;
ap.ptr = NULL;
}
return *this;
}
T& operator*()
{
return *ptr;
}
T* operator->()
{
return ptr;
}
private:
T * ptr;
};
二、對auto_ptr的缺陷的分析
從賦值運算符重載函數和拷貝構造函數來看,他們所使用的的是一個淺拷貝,所以如果auto_ptr對象之間出現了賦值和拷貝的情況,那麼就會出現,多個指針指向了同一個空間的情況,在對象聲明週期結束的時候,會出現同一塊空間被多次釋放的情況,所以爲了改變這種情況,auto_ptr使用的是一個管理權轉移的思想,所以在發生拷貝構造或者是賦值時,將原來的auto_ptr置爲空,這樣就解決了二次釋放的問題。但是這樣也有一個缺陷,那就是在用原來的auto_ptr訪問數據時,會出現內存錯誤,因爲此時的auto_ptr是一個NULL,用一個NULL去訪問數據顯然是C++不允許的。
三、對賦值運算符重載函數的一些說明
(1)賦值運算符重載
對於一個賦值運算符重載,我們有以下四點要注意的地方(注意這不僅僅是對於以上代碼而言,對於所有的賦值運算符重載都適用):
-
檢查返回值類型是否是該類類型的引用,並且返回值也是自身引用(即*this)。
因爲只有返回一個引用類型,那麼才能做到連續賦值(即s1=s2=s3),否則如果函數返回的不是一個引用,那麼在進行連續賦值的時候,編譯器就會報錯。 -
檢查函數的形參是否是const引用類型。
因爲如果傳入的參數不是引用,而是一個實例的話,那麼在實參對形參結合的這個過程中會調用拷貝構造函數。而如果是一個引用就會避免這種無端的銷燬,從而提高效率。之所以聲明爲const,是因爲在賦值運算符中,我們可以保證不會對對象進行修改。 -
檢查在賦值之前是否釋放掉自身已有內存。
如果我們忘記在賦值之前釋放掉自身已有內存,那麼就會出現內存泄漏。 -
檢查是否判斷自我賦值
因爲在賦值運算符重載函數中,我們首先要對自身對象進行釋放,然後纔開始賦值。如果沒有判斷自我賦值,那麼當是同一個對象進行賦值時,釋放掉自身對象,其實也是將傳入參數的內存也釋放掉了,因此就找不到要賦值的內容了。