国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

C++中的健壯指針和資源管理

2019-11-17 05:16:30
字體:
來源:轉載
供稿:網友

  我最喜歡的對資源的定義是:"任何在你的程序中獲得并在此后釋放的東西。"內存是一個相當明顯的資源的例子。它需要用new來獲得,用delete來釋放。同時也有許多其它類型的資源文件句柄、重要的片斷、Windows中的GDI資源,等等。將資源的概念推廣到程序中創建、釋放的所有對象也是十分方便的,無論對象是在堆中分配的還是在棧中或者是在全局作用于內生命的。

  我最喜歡的對資源的定義是:"任何在你的程序中獲得并在此后釋放的東西。"內存是一個相當明顯的資源的例子。它需要用new來獲得,用delete來釋放。同時也有許多其它類型的資源文件句柄、重要的片斷、Windows中的GDI資源,等等。將資源的概念推廣到程序中創建、釋放的所有對象也是十分方便的,無論對象是在堆中分配的還是在棧中或者是在全局作用于內生命的。

  資源及它們的所有權

  我最喜歡的對資源的定義是:"任何在你的程序中獲得并在此后釋放的東西?quot;內存是一個相當明顯的資源的例子。它需要用new來獲得,用delete來釋放。同時也有許多其它類型的資源文件句柄、重要的片斷、Windows中的GDI資源,等等。將資源的概念推廣到程序中創建、釋放的所有對象也是十分方便的,無論對象是在堆中分配的還是在棧中或者是在全局作用于內生命的。

  對于給定的資源的擁有著,是負責釋放資源的一個對象或者是一段代碼。所有權分立為兩種級別--自動的和顯式的(automatic and eXPlicit),假如一個對象的釋放是由語言本身的機制來保證的,這個對象的就是被自動地所有。例如,一個嵌入在其他對象中的對象,他的清除需要其他對象來在清除的時候保證。外面的對象被看作嵌入類的所有者。   類似地,每個在棧上創建的對象(作為自動變量)的釋放(破壞)是在控制流離開了對象被定義的作用域的時候保證的。這種情況下,作用于被看作是對象的所有者。注重所有的自動所有權都是和語言的其他機制相容的,包括異常。無論是如何退出作用域的--正常流程控制退出、一個break語句、一個return、一個goto、或者是一個throw--自動資源都可以被清除。

  到目前為止,一切都很好!問題是在引入指針、句柄和抽象的時候產生的。假如通過一個指針訪問一個對象的話,比如對象在堆中分配,C++不自動地關注它的釋放。程序員必須明確的用適當的程序方法來釋放這些資源。比如說,假如一個對象是通過調用new來創建的,它需要用delete往返收。一個文件是用CreateFile(Win32 API)打開的,它需要用CloseHandle來關閉。用EnterCritialSection進入的臨界區(Critical Section)需要LeaveCriticalSection退出,等等。一個"裸"指針,文件句柄,或者臨界區狀態沒有所有者來確保它們的最終釋放。基本的資源治理的前提就是確保每個資源都有他們的所有者。

  第一規則

  一個指針,一個句柄,一個臨界區狀態只有在我們將它們封裝入對象的時候才會擁有所有者。這就是我們的第一規則:在構造函數中分配資源,在析構函數中釋放資源。

  當你按照規則將所有資源封裝的時候,你可以保證你的程序中沒有任何的資源泄露。這點在當封裝對象(Encapsulating Object)在棧中建立或者嵌入在其他的對象中的時候非常明顯。但是對那些動態申請的對象呢?不要急!任何動態申請的東西都被看作一種資源,并且要按照上面提到的方法進行封裝。這一對象封裝對象的鏈不得不在某個地方終止。它最終終止在最高級的所有者,自動的或者是靜態的。這些分別是對離開作用域或者程序時釋放資源的保證。

  下面是資源封裝的一個經典例子。在一個多線程的應用程序中,線程之間共享對象的問題是通過用這樣一個對象聯系臨界區來解決的。每一個需要訪問共享資源的客戶需要獲得臨界區。例如,這可能是Win32下臨界區的實現方法。

class CritSect
{
 friend class Lock;
 public:
  CritSect () { InitializeCriticalSection (&_critSection); }
  ~CritSect () { DeleteCriticalSection (&_critSection); }
 PRivate
  void Acquire ()
  {
   EnterCriticalSection (&_critSection);
  }
  void Release ()
  {
   LeaveCriticalSection (&_critSection);
  }

CRITICAL_SECTION _critSection;
};
  這里聰明的部分是我們確保每一個進入臨界區的客戶最后都可以離開。"進入"臨界區的狀態是一種資源,并應當被封裝。封裝器通常被稱作一個鎖(lock)。

class Lock
{
 public:
  Lock (CritSect& critSect) : _critSect (critSect)
  {
   _critSect.Acquire ();
  }
  ~Lock ()
  {
   _critSect.Release ();
  }
 private
  CritSect & _critSect;
};
  鎖一般的用法如下:

void Shared::Act () throw (char *)
{
 Lock lock (_critSect);
 // perform action -- may throw
 // automatic destrUCtor of lock
}
  注重無論發生什么,臨界區都會借助于語言的機制保證釋放。

  還有一件需要記住的事情--每一種資源都需要被分別封裝。這是因為資源分配是一個非常輕易出錯的操作,是要資源是有限提供的。我們會假設一個失敗的資源分配會導致一個異常--事實上,這會經常的發生。所以假如你想試圖用一個石頭打兩只鳥的話,或者在一個構造函數中申請兩種形式的資源,你可能就會陷入麻煩。只要想想在一種資源分配成功但另一種失敗拋出異常時會發生什么。因為構造函數還沒有全部完成,析構函數不可能被調用,第一種資源就會發生泄露。

  這種情況可以非常簡單的避免。無論何時你有一個需要兩種以上資源的類時,寫兩個笑的封裝器將它們嵌入你的類中。每一個嵌入的構造都可以保證刪除,即使包裝類沒有構造完成。
Smart Pointers

  我們至今還沒有討論最常見類型的資源--用操作符new分配,此后用指針訪問的一個對象。我們需要為每個對象分別定義一個封裝類嗎?(事實上,C++標準模板庫已經有了一個模板類,叫做auto_ptr,其作用就是提供這種封裝。我們一會兒在回到auto_ptr。)讓我們從一個極其簡單、呆板但安全的東西開始。看下面的Smart Pointer模板類,它十分堅固,甚至無法實現。

template <class T>
class SPtr
{
 public:
  ~SPtr () { delete _p; }
  T * Operator->() { return _p; }
  T const * operator->() const { return _p; }
 protected:
  SPtr (): _p (0) {}
  explicit SPtr (T* p): _p (p) {}
  T * _p;
};
  為什么要把SPtr的構造函數設計為protected呢?假如我需要遵守第一條規則,那么我就必須這樣做。資源--在這里是class T的一個對象--必須在封裝器的構造函數中分配。但是我不能只簡單的調用new T,因為我不知道T的構造函數的參數。因為,在原則上,每一個T都有一個不同的構造函數;我需要為他定義個另外一個封裝器。模板的用處會很大,為每一個新的類,我可以通過繼續SPtr定義一個新的封裝器,并且提供一個特定的構造函數。

class SItem: public SPtr<Item>
{
 public:
  explicit SItem (int i)
  : SPtr<Item> (new Item (i)) {}
};
  為每一個類提供一個Smart Pointer真的值得嗎?說實話--不!他很有教學的價值,但是一旦你學會如何遵循第一規則的話,你就可以放松規則并使用一些高級的技術。這一技術是讓SPtr的構造函數成為public,但是只是是用它來做資源轉換(Resource Transfer)我的意思是用new操作符的結果直接作為SPtr的構造函數的參數,像這樣:

SPtr<Item> item (new Item (i));
  這個方法明顯更需要自控性,不只是你,而且包括你的程序小組的每個成員。他們都必須發誓出了作資源轉換外不把構造函數用在人以其他用途。幸運的是,這條規矩很輕易得以加強。只需要在源文件中查找所有的new即可。

  Resource Transfer

  到目前為止,我們所討論的一直是生命周期在一個單獨的作用域內的資源。現在我們要解決一個困難的問題--如何在不同的作用域間安全的傳遞資源。這一問題在當你處理容器的時候會變得十分明顯。你可以動態的創建一串對象,將它們存放至一個容器中,然后將它們取出,并且在最終安排它們。為了能夠讓這安全的工作--沒有泄露--對象需要改變其所有者。

  這個問題的一個非常顯而易見的解決方法是使用Smart Pointer,無論是在加入容器前還是還找到它們以后。這是他如何運作的,你加入Release方法到Smart Pointer中:

template <class T>
T * SPtr<T>::Release ()
{
T * pTmp = _p;
_p = 0;
return pTmp;
}
  注重在Release調用以后,Smart Pointer就不再是對象的所有者了--它內部的指針指向空。現在,調用了Release都必須是一個負責的人并且迅速隱藏返回的指針到新的所有者對象中。在我們的例子中,容器調用了Release,比如這個Stack的例子:

void Stack::Push (SPtr <Item> & item) throw (char *)
{
if (_top == maxStack)
throw "Stack overflow";
_arr [_top++] = item.Release ();
};
  同樣的,你也可以再你的代碼中用加強Release的可靠性。

  相應的Pop方法要做些什么呢?他應該釋放了資源并祈禱調用它的是一個負責的人而且立即作一個資源傳遞它到一個Smart Pointer?這聽起來并不好。Strong Pointers

  資源治理在內容索引(Windows NT Server上的一部分,現在是windows 2000)上工作,并且,我對這十分滿足。然后我開始想……這一方法是在這樣一個完整的系統中形成的,假如可以把它內建入語言的本身豈不是一件非常好?我提出了強指針(Strong Pointer)和弱指針(Weak Pointer)。一個Strong Pointer會在許多地方和我們這個SPtr相似--它在超出它的作用域后會清除他所指向的對象。資源傳遞會以強指針賦值的形式進行。也可以有Weak Pointer存在,它們用來訪問對象而不需要所有對象--比如可賦值的引用。

  任何指針都必須聲明為Strong或者Weak,并且語言應該來關注類型轉換的規定。例如,你不可以將Weak Pointer傳遞到一個需要Strong Pointer的地方,但是相反卻可以。Push方法可以接受一個Strong Pointer并且將它轉移到Stack中的Strong Pointer的序列中。Pop方法將會返回一個Strong Pointer。把Strong Pointer的引入語言將會使垃圾回收成為歷史。

  這里還有一個小問題--修改C++標準幾乎和競選美國總統一樣輕易。當我將我的注重告訴給Bjarne Stroutrup的時候,他看我的眼神似乎是我剛剛要向他借一千美元一樣。

  然后我忽然想到一個念頭。我可以自己實現Strong Pointers。究竟,它們都很想Smart Pointers。給它們一個拷貝構造函數并重載賦值操作符并不是一個大問題。事實上,這正是標準庫中的auto_ptr有的。重要的是對這些操作給出一個資源轉移的語法,但是這也不是很難。


template <class T>
SPtr<T>::SPtr (SPtr<T> & ptr)
{
_p = ptr.Release ();
}

template <class T>
void SPtr<T>::operator = (SPtr<T> & ptr)
{
if (_p != ptr._p)
{
delete _p;
_p = ptr.Release ();
}
}
  使這整個想法迅速成功的原因之一是我可以以值方式傳遞這種封裝指針!我有了我的蛋糕,并且也可以吃了。看這個Stack的新的實現:

class Stack
{
enum { maxStack = 3 };
public:
Stack ()
: _top (0)
{}
void Push (SPtr<Item> & item) throw (char *)
{
if (_top >= maxStack)
throw "Stack overflow";
_arr [_top++] = item;
}
SPtr<Item> Pop ()
{
if (_top == 0)
return SPtr<Item> ();
return _arr [--_top];
}
private
int _top;
SPtr<Item> _arr [maxStack];
};
  Pop方法強制客戶將其返回值賦給一個Strong Pointer,SPtr<Item>。任何試圖將他對一個普通指針的賦值都會產生一個編譯期錯誤,因為類型不匹配。此外,因為Pop以值方式返回一個Strong Pointer(在Pop的聲明時SPtr<Item>后面沒有&符號),編譯器在return時自動進行了一個資源轉換。他調用了operator =來從數組中提取一個Item,拷貝構造函數將他傳遞給調用者。調用者最后擁有了指向Pop賦值的Strong Pointer指向的一個Item。

  我馬上意識到我已經在某些東西之上了。我開始用了新的方法重寫原來的代碼。分析器(Parser)

  我過去有一個老的算術操作分析器,是用老的資源治理的技術寫的。分析器的作用是在分析樹中生成節點,節點是動態分配的。例如分析器的Expression方法生成一個表達式節點。我沒有時間用Strong Pointer去重寫這個分析器。我令Expression、Term和Factor方法以傳值的方式將Strong Pointer返回到Node中。看下面的Expression方法的實現:

SPtr<Node> Parser::Expression()
{
// Parse a term
SPtr<Node> pNode = Term ();
EToken token = _scanner.Token();
if ( token == tPlus token == tMinus )
{
// Expr := Term { ('+' '-') Term }
SPtr<MultiNode> pMultiNode = new SumNode (pNode);
do
{
_scanner.Accept();
SPtr<Node> pRight = Term ();
pMultiNode->AddChild (pRight, (token == tPlus));
token = _scanner.Token();
} while (token == tPlus token == tMinus);
pNode = up_cast<Node, MultiNode> (pMultiNode);
}
// otherwise Expr := Term
return pNode; // by value!
}
  最開始,Term方法被調用。他傳值返回一個指向Node的Strong Pointer并且馬上把它保存到我們自己的Strong Pointer,pNode中。假如下一個符號不是加號或者減號,我們就簡單的把這個SPtr以值返回,這樣就釋放了Node的所有權。另外一方面,假如下一個符號是加號或者減號,我們創建一個新的SumMode并且馬上(直接傳遞)將它儲存到MultiNode的一個Strong Pointer中。這里,SumNode是從MultiMode中繼續而來的,而MulitNode是從Node繼續而來的。原來的Node的所有權轉給了SumNode。

  只要是他們在被加號和減號分開的時候,我們就不斷的創建terms,我們將這些term轉移到我們的MultiNode中,同時MultiNode得到了所有權。最后,我們將指向MultiNode的Strong Pointer向上映射為指向Mode的Strong Pointer,并且將他返回調用著。

  我們需要對Strong Pointers進行顯式的向上映射,即使指針是被隱式的封裝。例如,一個MultiNode是一個Node,但是相同的is-a關系在SPtr<MultiNode>和SPtr<Node>之間并不存在,因為它們是分離的類(模板實例)并不存在繼續關系。up-cast模板是像下面這樣定義的:

template<class To, class From>
inline SPtr<To> up_cast (SPtr<From> & from)
{
return SPtr<To> (from.Release ());
}
  假如你的編譯器支持新加入標準的成員模板(member template)的話,你可以為SPtr<T>定義一個新的構造函數用來從接受一個class U。


template <class T>
template <class U> SPtr<T>::SPtr (SPrt<U> & uptr)
: _p (uptr.Release ())
{}
  這里的這個花招是模板在U不是T的子類的時候就不會編譯成功(換句話說,只在U is-a T的時候才會編譯)。這是因為uptr的緣故。Release()方法返回一個指向U的指針,并被賦值為_p,一個指向T的指針。所以假如U不是一個T的話,賦值會導致一個編譯時刻錯誤。

std::auto_ptr

  后來我意識到在STL中的auto_ptr模板,就是我的Strong Pointer。在那時候還有許多的實現差異(auto_ptr的Release方法并不將內部的指針清零--你的編譯器的庫很可能用的就是這種陳舊的實現),但是最后在標準被廣泛接受之前都被解決了。

  Transfer Semantics(轉換語義學)

  目前為止,我們一直在討論在C++程序中資源治理的方法。宗旨是將資源封裝到一些輕量級的類中,并由類負責它們的釋放。非凡的是,所有用new操作符分配的資源都會被儲存并傳遞進Strong Pointer(標準庫中的auto_ptr)的內部。

  這里的要害詞是傳遞(passing)。一個容器可以通過傳值返回一個Strong Pointer來安全的釋放資源。容器的客戶只能夠通過提供一個相應的Strong Pointer來保存這個資源。任何一個將結果賦給一個"裸"指針的做法都立即會被編譯器發現。

auto_ptr<Item> item = stack.Pop (); // ok
Item * p = stack.Pop (); // Error! Type mismatch.
  以傳值方式被傳遞的對象有value semantics 或者稱為 copy semantics。Strong Pointers是以值方式傳遞的--但是我們能說它們有copy semantics嗎?不是這樣的!它們所指向的對象肯定沒有被拷貝過。事實上,傳遞過后,源auto_ptr不在訪問原有的對象,并且目標auto_ptr成為了對象的唯一擁有者(但是往往auto_ptr的舊的實現即使在釋放后仍然保持著對對象的所有權)。自然而然的我們可以將這種新的行為稱作Transfer Semantics。

  拷貝構造函數(copy construcor)和賦值操作符定義了auto_ptr的Transfer Semantics,它們用了非const的auto_ptr引用作為它們的參數。

auto_ptr (auto_ptr<T> & ptr);
auto_ptr & operator = (auto_ptr<T> & ptr);
  這是因為它們確實改變了他們的源--剝奪了對資源的所有權。

  通過定義相應的拷貝構造函數和重載賦值操作符,你可以將Transfer Semantics加入到許多對象中。例如,許多Windows中的資源,比如動態建立的菜單或者位圖,可以用有Transfer Semantics的類來封裝。Strong Vectors

  標準庫只在auto_ptr中支持資源治理。甚至連最簡單的容器也不支持ownership semantics。你可能想將auto_ptr和標準容器組合到一起可能會管用,但是并不是這樣的。例如,你可能會這樣做,但是會發現你不能夠用標準的方法來進行索引。

vector< auto_ptr<Item> > autoVector;
  這種建造不會編譯成功;

Item * item = autoVector [0];
  另一方面,這會導致一個從autoVect到auto_ptr的所有權轉換:

auto_ptr<Item> item = autoVector [0];
  我們沒有選擇,只能夠構造我們自己的Strong Vector。最小的接口應該如下:

template <class T>
class auto_vector
{
public:
explicit auto_vector (size_t capacity = 0);
T const * operator [] (size_t i) const;
T * operator [] (size_t i);
void assign (size_t i, auto_ptr<T> & p);
void assign_direct (size_t i, T * p);
void push_back (auto_ptr<T> & p);
auto_ptr<T> pop_back ();
};
  你也許會發現一個非常防御性的設計態度。我決定不提供一個對vector的左值索引的訪問,取而代之,假如你想設定(set)一個值的話,你必須用assign或者assign_direct方法。我的觀點是,資源治理不應該被忽視,同時,也不應該在所有的地方濫用。在我的經驗里,一個strong vector經常被許多push_back方法充斥著。

  Strong vector最好用一個動態的Strong Pointers的數組來實現:

template <class T>
class auto_vector
{
private
void grow (size_t reqCapacity);

auto_ptr<T> *_arr;
size_t _capacity;
size_t _end;
};
  grow方法申請了一個很大的auto_ptr<T>的數組,將所有的東西從老的書組類轉移出來,在其中交換,并且刪除原來的數組。

  auto_vector的其他實現都是十分直接的,因為所有資源治理的復雜度都在auto_ptr中。例如,assign方法簡單的利用了重載的賦值操作符來刪除原有的對象并轉移資源到新的對象:


void assign (size_t i, auto_ptr<T> & p)
{
_arr [i] = p;
}
  我已經討論了push_back和pop_back方法。push_back方法傳值返回一個auto_ptr,因為它將所有權從auto_vector轉換到auto_ptr中。

  對auto_vector的索引訪問是借助auto_ptr的get方法來實現的,get簡單的返回一個內部指針。

T * operator [] (size_t i)
{
return _arr [i].get ();
}
  沒有容器可以沒有iterator。我們需要一個iterator讓auto_vector看起來更像一個普通的指針向量。非凡是,當我們廢棄iterator的時候,我們需要的是一個指針而不是auto_ptr。我們不希望一個auto_vector的iterator在無意中進行資源轉換。

template<class T>
class auto_iterator: public
iterator<random_access_iterator_tag, T *>
{
public:
auto_iterator () : _pp (0) {}
auto_iterator (auto_ptr<T> * pp) : _pp (pp) {}
bool operator != (auto_iterator<T> const & it) const
{ return it._pp != _pp; }
auto_iterator const & operator++ (int) { return _pp++; }
auto_iterator operator++ () { return ++_pp; }
T * operator * () { return _pp->get (); }
private
auto_ptr<T> * _pp;
};
  我們給auto_vect提供了標準的begin和end方法來找回iterator:

class auto_vector
{
public:
typedef auto_iterator<T> iterator;
iterator begin () { return _arr; }
iterator end () { return _arr + _end; }
};
  你也許會問我們是否要利用資源治理重新實現每一個標準的容器?幸運的是,不;事實是strong vector解決了大部分所有權的需求。當你把你的對象都安全的放置到一個strong vector中,你可以用所有其它的容器來重新安排(weak)pointer。

  設想,例如,你需要對一些動態分配的對象排序的時候。你將它們的指針保存到一個strong vector中。然后你用一個標準的vector來保存從strong vector中獲得的weak指針。你可以用標準的算法對這個vector進行排序。這種中介vector叫做permutation vector。相似的,你也可以用標準的maps, priority queues, heaps, hash tables等等。 三層交換技術 交換機與路由器密碼恢復 交換機的選購 路由器設置專題 路由故障處理手冊 數字化校園網解決方案 Code Inspection(編碼檢查)

  假如你嚴格遵照資源治理的條款,你就不會再資源泄露或者兩次刪除的地方碰到麻煩。你也降低了訪問野指針的幾率。同樣的,遵循原有的規則,用delete刪除用new申請的德指針,不要兩次刪除一個指針。你也不會碰到麻煩。但是,那個是更好的注重呢?

  這兩個方法有一個很大的不同點。就是和尋找傳統方法的bug相比,找到違反資源治理的規定要輕易的多。后者僅需要一個代碼檢測或者一個運行測試,而前者則在代碼中隱藏得很深,并需要很深的檢查。

  設想你要做一段傳統的代碼的內存泄露檢查。第一件事,你要做的就是grep所有在代碼中出現的new,你需要找出被分配空間地指針都作了什么。你需要確定導致刪除這個指針的所有的執行路徑。你需要檢查break語句,過程返回,異常。原有的指針可能賦給另一個指針,你對這個指針也要做相同的事。

  相比之下,對于一段用資源治理技術實現的代碼。你也用grep檢查所有的new,但是這次你只需要檢查鄰近的調用:

  ● 這是一個直接的Strong Pointer轉換,還是我們在一個構造函數的函數體中?

  ● 調用的返回知是否立即保存到對象中,構造函數中是否有可以產生異常的代碼。?

  ● 假如這樣的話析構函數中時候有delete?

  下一步,你需要用grep查找所有的release方法,并實施相同的檢查。

  不同點是需要檢查、理解單個執行路徑和只需要做一些本地的檢驗。這難道不是提醒你非結構化的和結構化的程序設計的不同嗎?原理上,你可以認為你可以應付goto,并且跟蹤所有的可能分支。另一方面,你可以將你的懷疑本地化為一段代碼。本地化在兩種情況下都是要害所在。

  在資源治理中的錯誤模式也比較輕易調試。最常見的bug是試圖訪問一個釋放過的strong pointer。這將導致一個錯誤,并且很輕易跟蹤。

  共享的所有權

  為每一個程序中的資源都找出或者指定一個所有者是一件很輕易的事情嗎?答案是出乎意料的,是!假如你發現了一些問題,這可能說明你的設計上存在問題。還有另一種情況就是共享所有權是最好的甚至是唯一的選擇。

  共享的責任分配給被共享的對象和它的客戶(client)。一個共享資源必須為它的所有者保持一個引用計數。另一方面,所有者再釋放資源的時候必須通報共享對象。最后一個釋放資源的需要在最后負責free的工作。

  最簡單的共享的實現是共享對象繼續引用計數的類RefCounted:


class RefCounted
{
public:
RefCounted () : _count (1) {}
int GetRefCount () const { return _count; }
void IncRefCount () { _count++; }
int DecRefCount () { return --_count; }
private
int _count;
};
  按照資源治理,一個引用計數是一種資源。假如你遵守它,你需要釋放它。當你意識到這一事實的時候,剩下的就變得簡單了。簡單的遵循規則--再構造函數中獲得引用計數,在析構函數中釋放。甚至有一個RefCounted的smart pointer等價物:

template <class T>
class RefPtr
{
public:
RefPtr (T * p) : _p (p) {}
RefPtr (RefPtr<T> & p)
{
_p = p._p;
_p->IncRefCount ();
}
~RefPtr ()
{
if (_p->DecRefCount () == 0)
delete _p;
}
private
T * _p;
};
  注重模板中的T不比成為RefCounted的后代,但是它必須有IncRefCount和DecRefCount的方法。當然,一個便于使用的RefPtr需要有一個重載的指針訪問操作符。在RefPtr中加入轉換語義學(transfer semantics)是讀者的工作。

  所有權網絡

  鏈表是資源治理分析中的一個很有意思的例子。假如你選擇表成為鏈(link)的所有者的話,你會陷入實現遞歸的所有權。每一個link都是它的繼續者的所有者,并且,相應的,余下的鏈表的所有者。下面是用smart pointer實現的一個表單元:

class Link
{
// ...
private
auto_ptr<Link> _next;
};
  最好的方法是,將連接控制封裝到一個弄構進行資源轉換的類中。

  對于雙鏈表呢?安全的做法是指明一個方向,如forward:

class DoubleLink
{
// ...
private
DoubleLink *_prev;
auto_ptr<DoubleLink> _next;
};
  注重不要創建環形鏈表。

  這給我們帶來了另外一個有趣的問題--資源治理可以處理環形的所有權嗎?它可以,用一個mark-and-sweep的算法。這里是實現這種方法的一個例子:

template<class T>
class CyclPtr
{
public:
CyclPtr (T * p)
:_p (p), _isBeingDeleted (false)
{}
~CyclPtr ()
{
_isBeingDeleted = true;
if (!_p->IsBeingDeleted ())
delete _p;

}

void Set (T * p)
{
_p = p;
}
bool IsBeingDeleted () const { return _isBeingDeleted; }
private
T * _p;
bool _isBeingDeleted;
};
  注重我們需要用class T來實現方法IsBeingDeleted,就像從CyclPtr繼續。對非凡的所有權網絡普通化是十分直接的。

  將原有代碼轉換為資源治理代碼

  假如你是一個經驗豐富的程序員,你一定會知道找資源的bug是一件浪費時間的痛苦的經歷。我不必說服你和你的團隊花費一點時間來熟悉資源治理是十分值得的。你可以立即開始用這個方法,無論你是在開始一個新項目或者是在一個項目的中期。轉換不必立即全部完成。下面是步驟。

  首先,在你的工程中建立基本的Strong Pointer。然后通過查找代碼中的new來開始封裝裸指針。

  最先封裝的是在過程中定義的臨時指針。簡單的將它們替換為auto_ptr并且刪除相應的delete。假如一個指針在過程中沒有被刪除而是被返回,用auto_ptr替換并在返回前調用release方法。在你做第二次傳遞的時候,你需要處理對release的調用。注重,即使是在這點,你的代碼也可能更加"精力充沛"--你會移出代碼中潛在的資源泄漏問題。

  下面是指向資源的裸指針。確保它們被獨立的封裝到auto_ptr中,或者在構造函數中分配在析構函數中釋放。假如你有傳遞所有權的行為的話,需要調用release方法。假如你有容器所有對象,用Strong Pointers重新實現它們。

  接下來,找到所有對release的方法調用并且盡力清除所有,假如一個release調用返回一個指針,將它修改傳值返回一個auto_ptr。

  重復著一過程,直到最后所有new和release的調用都在構造函數或者資源轉換的時候發生。這樣,你在你的代碼中處理了資源泄漏的問題。對其他資源進行相似的操作。

  你會發現資源治理清除了許多錯誤和異常處理帶來的復雜性。不僅僅你的代碼會變得精力充沛,它也會變得簡單并輕易維護。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 六安市| 阿城市| 克什克腾旗| 贵南县| 彭阳县| 永春县| 彭水| 儋州市| 雅安市| 石门县| 北海市| 兖州市| 岑巩县| 手游| 扶风县| 任丘市| 神木县| 砚山县| 南昌市| 新巴尔虎左旗| 开远市| 邻水| 万荣县| 永顺县| 库尔勒市| 丹棱县| 达日县| 吉林市| 汉寿县| 新兴县| 南投县| 普兰店市| 子长县| 恩施市| 于都县| 水富县| 莆田市| 安西县| 汝阳县| 海阳市| 永安市|