讀書筆記:C++中利用智能指標和值型類防止記憶體非法訪問
2013-03-10 18:18 by DVwei, 138 閱讀, 0 評論, 收藏, 編輯
在程式當中,經常會用到一些共用對象。一個具有指標成員的類,如果發生複製行為,一個指標複製到另一個指標時,兩個指標就指向同一個對象。此時就可以使用任一指標改變這個共用的對象。那麼,如果一個指標刪除了這個共用對象,那麼另一指標就成了懸垂指標,如果再對此對象進行操作時,就會發生記憶體訪問錯誤。而C++中無法判斷一個指標所指向的記憶體是否有效,這是非常危險的。
看下面一個例子:
複製代碼
class MyClass
{
public:
MyClass(int *p,int i): ptr(p),value(i) { }
~MyClass() { delete ptr; }
int get_share_value() { return *ptr; }
int get_value() { return value; }
void set_share_value(int i) { *ptr = i; }
void set_value(int i) { value = i; }
private:
int *ptr;
int value;
};
int main()
{int *p = new int(10);
MyClass test(p,20);
MyClass test2(test);
test.get_share_value();//return 10
test2.get_share_value();//return 10
test2.set_share_value(110);
test.get_share_value();//return 110
test.set_share_value(120);
test2.get_share_value();//return 120
return 0;
}
複製代碼
可以看到,使用任一指標都能改變共用的對象。而且運行這段代碼就能發現程式結束的時候發生錯誤。這是因為,程式結束時,編譯器會調用test和test2的解構函式,這樣就發生了兩次delete ptr,第一次delete釋放掉val,第二次delete時,指標所指向的記憶體已經無效,所以就會發生記憶體訪問錯誤。
為了避免出現重複delete同一對象的情況,可以定義智能指標來管理共用的對象。所謂智能指標,其實是引入一個使用計數,並建立一個計數類,用來記錄目前指向同一對象的指標數目,確保只剩下最後一個指向對象時才刪除對象。
代碼如下:
複製代碼
#include <iostream>
using namespace std;
class MyClass;
class Use_Ptr
{
friend class MyClass;
int *ptr;
size_t use;
Use_Ptr(int *p): ptr(p), use(1) { }
~Use_Ptr() { delete ptr; }
};
class MyClass
{
public:
MyClass(int *p,int i): ptr(new Use_Ptr(p)), value(i) { }
//發生複製行為時,記錄使用次數
MyClass(const MyClass &mc): ptr(mc.ptr), value(mc.value) { ++ptr->use; }
~MyClass() { if(--ptr->use == 0) delete ptr; }//撤銷對象前檢查使用計數是否為零,若為零,撤銷對象
MyClass& operator=(const MyClass&);
int get_share_value() { return *ptr->ptr; }
int get_value() { return value; }
void set_share_value(int i) { *ptr->ptr = i; }
void set_value(int i) { value = i; }
private:
Use_Ptr *ptr;
int value;
};
MyClass& MyClass::operator=(const MyClass &mc)
{
++mc.ptr->use;//使用use之前先將sm.ptr->use加一,防止對自身賦值
if(--ptr->use == 0)
delete ptr;
ptr = mc.ptr;
return *this;
}
int main()
{
int *p = new int(10);
MyClass test(p,20);
MyClass copy(test);
return 0;
}
複製代碼
這樣就確保了共用的對象在沒有指標指向它的時候才被delete掉,也就不會發生記憶體的非法訪問了。
另一種方法是把類設計成和實值型別具有相同的複製行為,即複製類對象的同時複製指標指向的值,而不是只複製指標的值。於是可以把上述MyClass這樣定義:
複製代碼
class MyClass
{
public:
MyClass(int &p,int i): ptr(new int(p)), value(i) { }
MyClass(const MyClass &mc): ptr(new int(*mc.ptr)), value(mc.value) { }//把值也複製
~MyClass() { delete ptr; }//這裡可以直接釋放
MyClass& operator=(const MyClass&);
int get_share_value() { return *ptr; }
int get_value() { return value; }
void set_share_value(int i) { *ptr = i; }
void set_value(int i) { value = i; }
private:
int *ptr;
int value;
};
MyClass& MyClass::operator=(const MyClass &mc)
{
*ptr = *mc.ptr;
value = mc.value;
return *this;
}
複製代碼
這樣每個指標都指向一個值相同但相互獨立的對象,delete後也不會影響到其他的對象。
這種方法也叫深度複製,在C#中可以通過實現clone介面來完成。