1. 程式人生 > >13.2:拷貝控制和資源管理

13.2:拷貝控制和資源管理

turn 還要 pub class apt 動態內存 並且 但是 方法

兩種選擇:
  • 類的行為像一個值:有自己的狀態,拷貝一個像值的對象時,副本和原對象是完全獨立的。改變副本不會改變原對象。
  • 類的行為像一個指針:類是共享狀態,當拷貝這個對象時,原對象和副本對象使用相同的底層數據,改變副本也會改變原對象。

1.行為像值的類 拷貝對象,而不是拷貝指針。 代碼如下: class HasPtr{ public: HasPtr(const string &s = string()):ps(new string(s)), i(0) {} //對ps指向的string,每個HasPtr對象都有自己的拷貝 HasPtr(const HasPtr &p):ps(new string(*p.ps)), i(p.i) {} HasPtr& operator = (const HasPtr&); ~HasPtr() { delete ps; } private: string *ps; int i; }; HaPtr& HasPtr::operator = (const HasPtr &rhs) { auto newp = new string(*rhs.ps); //拷貝底層string delete ps; //釋放舊內存 ps = newp; //從右側對象拷貝數據到本對象 i = rhs.i; return *this; //返回本對象。 } 註意要防範自賦值操作的錯誤。 錯誤代碼: HaPtr& HasPtr::operator = (const HasPtr &rhs) { delete ps; //釋放對象指向的string //但是如果rhs和*this是同一對象,就將從已經釋放的內存中拷貝數據 ps = new string(*(rhs.ps)); i = rhs.i; return *this; //返回本對象。 }
2.行為像指針的類 需要拷貝指針,而不是拷貝指針所指的對象。 對於共享資源,最好的方法是使用shared_ptr。當時如果希望直接管理資源,最好使用引用計數(使用自定義而非shared_ptr)。 工作方式如下:
  • 在構造函數中,除了初始化對象外,還要創建一個引用計數,來記錄共享狀態。初始化時只有一個對象,引用計數初始化為1.
  • 拷貝構造函數不分配新的計數器,而是拷貝給定對象的數據成員,包括計數器。並且要遞增共享的計數器。
  • 析構函數遞減計數器,當計數器變為0時,析構函數就可以釋放成員。
  • 拷貝賦值運算符要遞增右側運算對象的計數器,遞減左側對象的計數器。如果左側運算對象計數器為0,拷貝賦值運算符就必須銷毀此對象的狀態。
為了更新引用計數,將計數器保存在動態內存中,當創建對象,分配一個新的計數器,拷貝或賦值對象時,拷貝指向計數器的指針。 代碼如下: class HasPtr{ public: //構造函數分配新的string和新的計數器1 HasPtr(const string &s = string()):ps(new string(s)),i(0),use(new size_t(1)){} //拷貝構造函數拷貝三個成員,並遞增計數器 HasPtr(const HasPtr &p):ps(p.ps), i(p.i), use(p.use) { ++*use; } HasPtr& operator = (const HasPtr&); ~HasPtr(); private: string *ps; int i; size_t *use; //計數器,記錄共享*ps的成員 }; HasPtr& HasPtr::operator = (const HasPtr &rhs) { ++*rhs.use; //遞增右側運算對象的引用計數 --*use; //遞減本對象的引用計數 if(*use == 0) { delete ps; //釋放對象 delete use; //釋放計數器 } //拷貝rhs對象 ps = rhs.ps; i = rhs.i; use = rhs.use; return *this; } HasPtr::~HasPtr() { --*use; //遞減對象計數器 if(*use == 0) { delete ps; //釋放對象 delete use; //釋放計數器 } }

13.2:拷貝控制和資源管理