Symbian 學習日誌(四. NewL 和 NewLC) (轉的總結)

我不是大明發表於2009-02-06

 

理解 NewL ConstructL NewLC ELeave

初學Symbian開發,第一件感覺迷惑的事情是CleanupStack 第二件肯定是隨處可見的NewL,NewLC,ConstructL。
這些函式的出現依然和記憶體洩漏有關,這是一種被稱為兩步構造的機制,英文叫Two-phase Construction。
我知道C++裡面的 new 操作符實際上完成2件事,第一根據物件類的大小在堆上分配一塊記憶體並獲得指向記憶體的
指標,第二利用指標呼叫類的建構函式,最後把指標返回。

在Symbian上這樣做是有隱患的,就是當你分配好了記憶體,但是呼叫建構函式的時候程式意外退出了,這樣會造成
剛才分配的記憶體產生洩漏。只有那些放入CleanupStack的記憶體,在程式意外結束後會被釋放,new 分配的記憶體在
呼叫建構函式之前還沒有被放入CleanupStack呢。

Symbian的設計師為了解決這個問題,給所有開發者設計了一個編寫程式的定式,這就是Two-phase Construction

具體是這樣的:

把普通的new 操作分為2個步驟來進行,第一步只分配記憶體,當分配的記憶體被放入cleanupstack後,第二步進行構造。
但是在C++裡 如何阻止編譯器的new操作不呼叫建構函式呢?這個貌似不可以。。。
於是Symbian的設計者作了個規定,類在建構函式裡不要做任何可能產生異常的操作,只能做那些絕對安全的事情,比如
簡單的變數賦值,然後提供一個名字叫 ConstructL的函式,在這個函式裡做所有類的初始化工作,當然包括那些危險
的可能導致異常的操作。

那麼 Symbian的 類構造就變成了這樣
比如 有一個叫 CFoo 的類,我想宣告一個指標 p,建立一個CFoo的物件賦給 p
CFoo *p = new(ELeave) CFoo();
CleanupStack::PushL(p);
p->ConstructL();
CleanupStack::Pop();

這樣寫是不是有點太羅嗦?每個物件都要用4條語句建立,如果是頻繁使用實在是太麻煩了。
於是Symbian的設計者又作了一個規定,每個類要實現一個NewL的static函式來完成上面的4條語句的工作
class CFoo
{
public:
 static CFoo *NewL()
 {
  CFoo *self = new(ELeave) CFoo();
  CleanupStack::PushL(self);
  self->ConstructL();
  CleanupStack::Pop();
  return self;
 }
}

有了NewL以後,呼叫CFoo的類的程式簡化了
CFoo *p = CFoo::NewL();

那麼NewLC又是什麼呢?和NewL有什麼不同?
有些類是這樣的,他們提供一些方法,需要在物件建立完成後執行,但是這些方法也是會產生異常的比如
CFoo 如果有一個方法叫 DoSomethingL()

那麼程式可以這樣寫嗎?
CFoo *p = CFoo::NewL();
p->DoSomethingL();

顯然這樣寫是有問題的正確的寫法是
CFoo *p = CFoo::NewL();
CleanupStack::PushL(p);
p->DoSomethingL();
CleanupStack::Pop();

天啊,又是4條語句,太麻煩了。要知道在NewL結尾我們剛剛把CFoo的指標從CleanupStack裡拿出來,馬上就又放了進去。
是不是可以簡化呢,那好我們再節約2條語句
NewL去掉結尾的CleanupStack::Pop();
 static CFoo *NewL()
 {
  CFoo *self = new(ELeave) CFoo();
  CleanupStack::PushL(self);
  self->ConstructL();
  return self;
 }
呼叫去掉CleanupStack::PushL
CFoo *p = CFoo::NewL();
p->DoSomethingL();
CleanupStack::Pop();

Symbian的設計者又規定了,具有以上行為的NewL 應該叫NewLC。表示指標返回後,沒有從CleanupStack裡取出,你可以繼續呼叫一個危險的操作,在最後呼叫CleanupStack::Pop();

我發現 NewL 可以通過呼叫NewLC實現。
那麼一個符合Symbian設計師的N條規定的類應該這樣寫

class CFoo
{
public:
 static CFoo *NewLC()
 {
  CFoo *self = new(ELeave) CFoo();
  CleanupStack::PushL(self);
  self->ConstructL();
  return self;
 }

 static CFoo *NewL()
 {
  CFoo *self = NewLC();
  CleanupStack::Pop();
  return self;
 }

 virtual ~CFoo()
 {
 }

protected:
 CFoo()
 {
 }

 void ConstructL()
 {
 // ....
 }
}

這裡注意 CFoo的建構函式不能是Public的,為了防止使用者用new 或者在棧上建立物件。
解構函式要寫成虛擬函式,這是純C++問題,不明白的去看 More Effective C++

要說明一下,以上的寫法是Symbian極力推薦的,但是不是硬性規定的,你只要保證沒有記憶體洩漏
可以不這麼寫。
我個人還是推薦這樣,這樣的程式碼寫Symbian程式的人都可以很好地理解。

最後說一下 new 之後為什麼要有一個 (ELeave)。
new操作符是被Symbian過載過了,ELeave是給new的一個引數,他的意思是告訴new當無法分配記憶體時
程式就退出。比如記憶體不足的時候。

所以我們用了ELeave的話 就不用檢查new 返回的指標了,能返回就一定是對的
如果出了錯程式就結束掉了,new根本就不會返回。

NewL NewLC 是Symbian程式標誌性的函式,所以有個Symbian開發的資源站點就叫 www.newlc.com

相關文章