Symbian異常三步曲之二清除棧

一、爲什麼使用清除棧
清除棧主要是用來處理在異常退出發生時那些或許可以稱之爲被遺棄或泄漏的內存。
看下面的代碼:
void UnsafeFunctionL()
{
       CClanger* clanger = new(ELeave) CClanger();
       clanger->InitializeL();
       ……..//略去
        delete clanger;
}
 
分析:一旦clanger->InitializeL()運行時發生異常,則clanger所指的堆內存將會泄漏,這個例子我們在三步曲之一中提到過。
那麼有什麼辦法來處理這種潛在的錯誤呢?
我們很容易就想到,可以使用TRAPD捕獲異常來進行堆內存的釋放,即將上面代碼進行如下修改:
void UnsafeFunctionL()
{
       CClanger* clanger = new(ELeave) CClanger();
       TRAPD(error,clanger->InitializeL());
       If(KErrNone != error)
{
               delete clanger;
}
       ……..//略去
       delete clanger;
}
也就是說通過TRAPD捕獲異常,然後對異常錯誤碼進行判斷,如果確實發生異常了,那麼就調用delete來釋放堆內存。
當然,上面辦法是可行的,但是,如果存在多個可能異常的函數,那麼我們都使用TRAPD捕獲異常的話,所造成的系統開銷就會非常之大,清除棧就是爲了解決這個問題而存在的,並且它很好的解決了這樣類型的一系列問題。
 
二、使用清除棧
類CleanupStack定義在頭文件e32base.h中,可以通過這個類提供的靜態成員函數來訪問清除棧。
清除棧的原理
在調用可能發生異常退出的代碼之前,非異常退出安全的對象應該被置於清除棧上。這可以保證在異常退出發生時,這些對象可以被正確的銷燬。當發生異常退出時,清除棧能夠將所有已經置於其上的對象回收。
 
下面就是一個使用清除棧的小例子:
void SafeFunctionL()
{
       CClanger* clanger = new(ELeave) CClanger;
       CleanupStack::PushL(clanger);
       clanger->InitializeL();
       clanger->DoSomethingElseL();
       CleanupStack::Pop(clanger);
       delete clanger;
}
實際上這個函數中的最後兩條語句
CleanupStack::Pop(clanger);
delete clanger;
可以使用CleanupStack::PopAndDestroy(clanger);這一條語句來替代,它們是等價的。
如果在調用clanger->InitializeL();或clanger->DoSomethingElseL();的時候異常退出了,clanger對象就會被清除棧銷燬。
 
順序問題:
對象必須以嚴格的順序壓入和彈出清除棧,一組Pop()操作必須與PushL()調用的順序相反。可能會發生調用Pop()或PopAndDestroy()彈出對象而沒有命名這些對象的情況,但最好還是要對彈出對象進行命名。
舉例:
void ContrivedExampleL()
{
       CSiamese* sealPoint = NewL(ESeal);
       CleanupStack::PushL(sealPoint);
       CSiamese* chocolatePoint = NewL(EChocolate);
       CleanupStack::PushL(chocolatePoint);
       CSiamese* violetPoint = NewL(EViolet);
       CleanupStack::PushL(violetPoint);
       CSiamese* bluePoint = NewL(EBlue);
       CleanupStack::PushL(bluePoint);
      
       sealPoint->CatchMouseL();//入清除棧語句放在可能異常退出的代碼之前
      
       CleanupStack::PopAndDestroy(bluePoint);
       CleanupStack::PopAndDestroy(violetPoint);
       CleanupStack::PopAndDestroy(chocolatetPoint);
       CleanupStack::PopAndDestroy(sealPoint);
}
 
可以看到出棧的順序和入棧的順序正好是相反的,不過在這裏顯得複雜了一點,上面的四個出清除棧語句可以使用CleanupStack::PopAndDestroy(4);或CleanupStack::PopAndDestroy(4,sealPoint);進行代替效果基本是一樣的。
 
創建在堆上的對象由誰銷燬?
對於一個對象,清除永遠不能超過一次。如果清除棧上有一個指向對象的指針,而後來又保存到其它地方了,譬如成了另一個對象的成員變量,而這個對象在異常退出後仍可以被訪問,那麼就應該從清除棧上彈出這個指針。如果在清除棧上保留了這個指針,那麼清除棧會銷燬它所指的對象,但是保存了該指針的對象也會試圖通過析構函數銷燬這個指針所指的對象。
因此,對象應該只被清除棧或另一個對象所引用,而不能同時被兩者引用。
類似的,永遠不要將類成員變量壓入清除棧。
小例子:
void TransferOwnershipExampleL()
{
       CClanger* clanger = new( ELeave ) CClanger();
       CleanupStack::PushL(clanger);//壓入清除棧
       iMemberObject->TakeOwnershipL(clanger);//類成員iMemberObject獲得了對象所有權
       CleanupStack::Pop(clanger);//這裏就必須從清除棧中彈出對象指針
                          //調用完異常代碼後將對象指針從清除棧中彈出
}
 
命名問題:
如果有對象被壓入清除棧,並直至函數返回時還保留在清除棧上,則該函數應該以”C”作爲後綴。這就告訴函數調用者,如果函數正常返回,清除棧上仍然有多餘的對象。
CSiamese* CSiamese::NewLC(TpointColor aPointColor)
{
       CSiamese* me = new( ELeave ) CSiamese( aPointColor );
       CleanupStack::PushL( me );
       me->ConstructL();
       return me;
}
上面的函數實際是用在對象的二階段構造中,其中,壓入清除棧後並沒有彈出,因此命名時必須要用”C”結尾。
 
三、對非CBase派生類使用清除棧
 
看一下CleanupStack::PushL()的三種重載形式:
(1)CleanupStack::PushL(CBase* aPtr)
(2)CleanupStack::PushL(TAny*)
(3)CleanupStack::PushL(TCleanupItem)
 
第一種形式:用在CBase的派生類對象上,當pop時,會調用該派生類對象的析構函數,跟C++一樣,先調用派生層次最深類的析構函數,然後沿着派生層次順次向上調用析構函數,最後調用CBase的空析構函數。
 
第二種形式:如果在定義一個C類時,忘記了從CBase派生,那麼就會很危險,因爲在調用PushL時,實際上調用的是CleanupStack::PushL(TAny*)這個方法,這樣在pop時就不會調用這個類的析構函數,僅僅是清除它所指向的堆內存而已。實際上該方法被用來將沒有析構函數的、基於堆的對象的指針壓入清除棧(比如T類對象或結構體)。
 
第三種形式:接收一個TcleanupItem類型對象作爲參數,這個函數可以使我們將其他類型的對象或那些具有定製的清除例程的對象壓入清除棧。TCleanupItem對象封裝了一個指向要保存在清除棧上對象的指針和一個指向提供相應對象清除操作的函數指針。
 
另外,Symbian OS還提供的三個用於清除的工具函數,分別是Release()、Delete()、Close(),都會生成一個TCleanupItem對象讓我們能夠自己定義清除的過程。並結合下面三個入棧方法。當然,這裏入棧的對象引用,可以是創建在堆上的任意對象,比如C類對象,R類對象,T類對象,M類對象等等均可。
(1)       CleanupReleasePushL( T& aRef注意參數不是T*) //
 
異常退出的處理或PopAndDestroy()調用將對T類對象調用Release()
舉例:
class MExtraTerrestrial
{
  public:
          virtual void CommunicateL() = 0;
          …..//出於整潔,略去接口其他代碼
          virtual void Release() = 0;
}
 
class CClanger : public CBase , MExtraTerrestrial
{
  public:
          static MExtraTerrestrial* NewL();
          virtual void CommunicateL();
          virtual void Release();
  private:
          CClanger();
          ~CClanger();
  private:
          ……..
}
void TestMixinL()
{
  MExtraTerrestrial* clanger = Clanger::NewL();
  CleanupReleasePushL(*clanger);//參數不是指針,這點和普通PushL不同
  ……..//執行可能發生異常退出的代碼
  CleanupStack::PopAndDestroy(clanger);//這裏是指向對象的指針
}
注意:入清除棧和出清除棧時的參數是不一樣的。
(2)       CleanupDeletePushL( T& aRef)
通過使用CleanupDeletePushL()可以使異常退出處理或PopAndDestroy()調用對對象施以delete操作,進而調用對象的析構函數,並且相應的堆內存也會被釋放。這就類似於使用接受CBase指針的CleanupStack::PushL()重載函數。當必須要將M派生類指針置於清除棧上時,該函數尤爲有用。
 
(3)       CleanupClosePushL( T& aRef類對象內置了Close()方法,不用另外添加了。) //R
如果對象是通過CleanupClosePushL()壓入清除棧的話,則異常退出處理或PopAndDestroy()調用將對對象施以close()操作。
void UseFilesystemL()
{
  RFs theFs;
  User::LeaveIfError(theFs.Connect());
  CleanupClosePushL(theFs);
  CleanupStack::PopAndDestroy(&theFs);
}
 
Symbian OS 提供的這三個工具模板函數,它們分別對應於Release()、Delete()、Close()這三個清除方法,這3個工具函數都會生成一個TCleanupItem類型的對象並將其壓入清除棧中。
 
小結
系統中每個分配了資源的可執行單元(或者線程)都有它自己的清理棧和最高級別的TRAP/TRAPD來做異常處理和一些退出後的善後工作。之所以引入清除棧,就是爲了解決堆內存泄漏的問題,注意是堆內存,如果對象被創建在了棧上的話,這是不關清除棧的事的,因爲棧上的對象所佔空間由棧自動管理。
 
注意:對於C類對象而言,CleanupStack::Pop()方法僅僅是將C類對象指針從清除棧中彈出了而已,並沒有調用這個C類對象的析構函數,若要析構,需要再加語句delete c,或者可以直接使用CleanupStack::PopAndDestroy()同時完成上面兩個動作。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章