Serialize 的聯想 (2)

侯捷 1998.12.15


●william 說:

> 有點想不透, 為什麼 MFC 不使用在 OO 界早已習稱的 "persistence" 術語,
> 改弄出一個 "serialization" 術語?
> 單就字面上來說, "serialization" 一詞, 很容易跟 database、OS 界常談的
> "serializable"、"serializability" 相混淆。
>
> 是因為 MFC 的 object persistence 只能做到 "serial (sequential) read/write" 嗎?
> 那麼, 對一個 object collection 所做的 serialization 動作,
> 就一定非得是 sequential 而不能 random access 或 partial update 嗎?
> 我想不是這樣的, 因為 MFC 的 collection classes 有個叫做
> SerializeElements 的 virtual 成員函數,
> 也就是說:程式員可以自己做出最適合的存取方式, 即使是跳躍存取也行。


●QueenLin 說:

> SerializeElements是free function template, 不是collection classes
> 的成員函數.

> 儘管MFC提供persistence的機制,可以永續物件的生命週期,但確實以這個機制
> 所展現的功能來看, 它只是"依序"的將物件資料寫到外部儲存體中,即使可以
> 覆寫SerializeElements function, 但這還是循序的方式. 用"persistence"
> 確實可符合OO 界用語的一致性降也避免術語的混淆, 不過"serialization"
> 一詞也正說明MFC對persistence的支援有限及其特性.

> 要能隨機處理存在於外部儲存體中的物件, 那得多付出一些額外的努力, 因為
> 到了這兒目前MFC幫不上什麼大忙, 而這些工作又跟Database為我們所做的有
> 關. 至於該不該用Database system, 這需要更多的因素綜合考量. 而我們的
> 作法就是自行實作我們所要的random access, partial update功能.


●jjhou 的整理

原本不曾注意過 MFC's SerializeElements()。由於 william 的提起和QueenLin 的回覆,我決定把它搞清楚。既然搞清楚了,索性整理下來供大家參考。如有錯誤,請指正。

◆template classes in MFC

MFC 提供 6 個 template classes:

CArray,
CList,
CMap,
CTypedPtrArray,
CTypedPtrList,
CTypedPtrMap

◆SerializeElements() 與 CArray, CList, CMap

CArray, CList, CMap 這三個 template class 都只能放置相同 type 的元素(若要運用 polymorphism 性質在 template container 中放置CObject-derived type,則必須使用 CTypedPtrArray, CTypedPtrList, CTypedPtrMap 三者)。

由於每一個元素的 type 都相同,因此上述三個 template classes 就不必為每個元素寫出 class info(只要整個 container 寫一次就足夠)。因此,default 情況下它們的 Serialize() 都是以 bitwise 方式將整份 container 寫檔(或讀檔)。觀察 AFXTEMPL.H,可看到 CArray, CList, CMap 的Serialize() 都呼叫 SerializeElements()(一個 global template function)如下:

template<class TYPE>
void AFXAPI SerializeElements(CArchive& ar, TYPE* pElements, int nCount)
{
  ASSERT(nCount == 0 ||
  AfxIsValidAddress(pElements, nCount * sizeof(TYPE)));

  // default is bit-wise read/write
  if (ar.IsStoring())
    ar.Write((void*)pElements, nCount * sizeof(TYPE));
  else
    ar.Read((void*)pElements, nCount * sizeof(TYPE));
}


此處 ar.Write() 和 ar.Read() 會喚起 ar 內含的 CFile member 的Write() 和 Read(),執行 bitwise 檔案讀寫。(ref <多型與虛擬> chap5 MFC-Lite sample, p285, #0298 and #0305)

◆SerializeElements() 的運用

如果你滿意於 CArray, CList, CMap 對每個元素的 bitwise 檔案讀寫,那麼不管你使用什麼樣的 type 做為元素型別,都不必再多費手腳做其他額外工作。

如果你不喜歡上述的 bitwise 檔案讀寫,可改寫 SerializeElements()。假設我們設計一個 CMyStruct(參考 MFC COLLECT sample):

class CMyStruct
{
  int m_int;
  float m_float;
  CString m_str;
};


並打算這麼用:

CMap<DWORD,DWORD,CMyStruct*,CMyStruct*&> m_mapDWordToMyStruct;

那麼,如果不喜歡 bitwise 檔案讀寫,我們可改寫 SerializeElements() 如下:

// in VC 5.0(注意:VC 4.2 有不同的寫法)
template <> void AFXAPI SerializeElements<CMyStruct*>(CArchive& ar,
CMyStruct** ppElements,
int nCount)
{
  ... // maybe call CMyStruct's Serialize()
      // if CMyStruct derived from CObject and use IMPLEMENT_SERIAL
}


◆SerializeElements() 的兄弟

ConstructElements() 和 DestructElements()

◆SerializeElements() 的檢討

1. SerializeElements() 只與 CArray, CList, CMap 有關。
2. 它是個 template function,可改寫(override)。
3. 即使以很奇怪的方式來改寫 SerializeElements(),例如跳躍讀寫 array 或 list 或 map 中的內容,讀與寫的過程仍然是循序。換言之,怎麼寫出去, 就怎麼讀進來;寫出去多少,就讀進來多少;完全無法用於 random access 或 partial update。

◆CTypedPtrArray, CTypedPtrList, CTypedPtrMap 的情況呢?

下面是這三個 template class 的宣告(in AFXTEMPL.H):

template<class BASE_CLASS, class TYPE>
class CTypedPtrArray : public BASE_CLASS
{
...
};

template<class BASE_CLASS, class TYPE>
class CTypedPtrList : public BASE_CLASS
{
...
};

template<class BASE_CLASS, class KEY, class VALUE>
class CTypedPtrMap : public BASE_CLASS
{
...
};


其中都沒有改寫 Serialize(),因此若喚起上述任一個 class 的Serialize(),即是喚起其 BASE_CLASS 的 Serialize()。閱讀<深入淺出 MFC> p503 或 <多型與虛擬> p284, p289,即可窺知CDWordArray 和 CObList 的 Serialize() 函式定義。

註:<深入淺出 MFC> 談的是真正的 MFC source, <多型與虛擬> chap5 談的是模擬的、簡易的、可適用任何平台的 MFC-Lite。

●我的疑惑之一

IMPLEMENT_SERIAL 巨集接受一個 "schema no" 參數。當 Serialize() 讀到不合版本號碼的 class info,便會自動給一個 msg box (<深入淺出 MFC> p634)。

但是,以舊版軟體讀取新版檔案,或以新版軟體讀取舊版檔案,通常我們並不希望一檢查出版本不符便嚴峻拒絕。通常我們希望做出一個相容性。
如何避免那個 "Unexpected file format" msg box 跑出來?是否有什麼 virtual function 可改寫?我尚未查閱 MFC source 或 MFC manual,如您有此經驗,請指教,謝謝。

●我的疑惑之二

STL 已成為標準 C++ 的一部份。可以預見,未來使用 STL 的情況將與日俱增。但 STL 沒有實作 persistence(此屬必然,因為 STL 希望 platform-independent;而 persistence 涉及檔案系統,卻又一定是 platform-dependent)。

STL 無 persistence 性質,這點大家有何看法?是否(可能)有什麼產品可搭配?或難道 STL user 得自行將 container 元素一一取出,自行完成檔案存取動作?這樣的姿態好像不夠優雅,好像有點遜。

●我的疑惑之三

MFC collection classes 是否可完全取代 STL?彼此優劣如何?大家看法如何?

--- the end