fw: 侯捷--MFCLite

来源:互联网 发布:qq三国双吸马超克制js 编辑:程序博客网 时间:2024/04/20 19:41

汗如雨下

寄件者: xioax
收件者: 侯 捷
傳送日期: 2002年1月22日 AM 09:36
主旨: 關於MFCLite的一些問題﹗

候老師﹕
您好﹐我是一名剛從浙大畢業的學生﹐也是您的忠實讀者。最近我在閱讀您的MFCLite﹐發現了些問題﹐列舉如下(如有不對的地方﹐還請老師指點)﹕

1﹑對象持久性。由於兩年前我曾仔細研究過Turbo Vision的持久性﹐對這個問題比較熟悉。所以一開始我就發現了您的代碼中所存在的問題﹐下面是我的測試程序(部份)﹐還有輸出文件的內容﹕
...
CSquare *psqr1 = new CSquare () ;
CSquare *psqr2 = psqr1 ; // psqr1 and psqr2 point to same object
assert (psqr1 == psqr2) ;
{
? CFile write ("test.tmp", CFile::modeWrite) ;
? CArchive store (&write, CArchive::store) ;
? store << psqr1 << psqr2 ;
}
delete psqr1 ;
psqr1 = psqr2 = NULL ;
{
? CFile read ("test.tmp", CFile::modeRead) ;
? CArchive load (&read, CArchive::load) ;
? load >> psqr1 >> psqr2 ;
}
assert (psqr1 == psqr2) ; // here is an assert failure, not point to same object!
...
00 00 07 00 43 53 71 75 61 72 65 00 00 00 00 00 ....CSquare.....
00 00 00 00 00 00 00 00 00 07 00 43 53 71 75 61 ...........CSqua
72 65 00 00 00 00 00 00 00 00 00 00 00 00 re..............

psqr1和psqr2指向同一對象﹐寫入文件時應該只有一份﹐但是在您的實現中卻寫了兩次﹗導致了讀出時﹐psqr1和psqr2指向了不同的對象。顯然這是不正確的。我覺得對於C++ 對象持久性而言﹐最重要的問題﹕一個是如何保存相關的類信息﹐另一個就是如何解決上述問題﹗在您的兩本著作《多形與虛擬》﹑《深入淺出MFC》中對前者都有很精闢的論述﹐唯獨後者一點也沒有提及﹐不能不說是一個很大的瑕疵。對於如何解決這個問題也不是很困難﹐只要先實現CMapPtrtoPtr和CPtrArray﹐在寫入時先查map如果已寫過﹐就只把輸出序號寫入文件﹐如果沒有就把對象的地址和輸出序號插入map﹐再把數據寫入文件。讀出時﹐遇到第二種情況(即文件中有實際數據,而非只是序號)﹐就先創建一個對象把數據讀出﹐接著再把新建對象的
地址加到數組array尾端﹐遇到第一種情況﹐就以輸出序號為索引直接從數組中得到對象(由於寫入和讀出的順序一樣﹐僅用輸出序號就可以完全解決問題)。

2﹑CPtrList的實現有內存泄漏﹐下面是相關代碼和我的修改(紅色)﹕
...
我覺得就MFCLite的目的而言﹐完全沒有必要象MFC那樣實現一些比較高級的內存管理技巧。因為一來這些內存管理與MFC的應用程序架構完全沒有關係﹐二來加大了讀者的閱讀難度﹐三來加大了MFCLite的出錯機會。如果CPtrList使用CObList的實現方法就沒有這麼多問題﹗
另外﹐您說在下面的代碼中﹐刪除CPtrList中的元素程序會崩潰﹐不知是不是上面的原因﹖

void CMultiDocTemplate::RemoveDocument(CDocument* pDoc)
{
CDocTemplate::RemoveDocument(pDoc);
// m_docList.RemoveAt(m_docList.Find(pDoc)); // 把 doc 節點移走 <- 有問題. crash!
}

3﹑CDocument::OnOpenDocument和CDocument::OnSaveDocument有內存泄漏﹕

BOOL CDocument::OnOpenDocument(const string& strFileName)
{
? CFile* pFile = new CFile(strFileName.c_str(), CFile::modeRead);
? CArchive loadArchive(pFile, CArchive::load);
? Serialize(loadArchive);
? loadArchive.Close();
? pFile->Close();
? // should delete pFile
? delete pFile ;
? return TRUE;
}
BOOL CDocument::OnSaveDocument(const string& strFileName)
{
? CFile* pFile = new CFile(strFileName.c_str(), CFile::modeWrite);
? CArchive saveArchive(pFile, CArchive::store);
? Serialize(saveArchive);
? saveArchive.Close();
? pFile->Close();
? // should delete pFile
? delete pFile ;
? return TRUE;
}

4﹑我發現CObject的析構函數不是虛擬的。我想一定是老師的一時疏忽。:-)面對幾千行的
代碼﹐誰敢保證沒錯呢﹖﹗

5﹑MFCLite對新建文件﹑打開文件和保存文件模擬得很好﹔但是對關閉窗口﹐進而造成文檔的摧毀卻模擬得不夠。其實在C++中由於析構錯誤造成的內存泄漏﹐往往是最常見﹐也是最難
排除的Bug。希望在MFCLite的新版本能看到相應的模擬﹗


雖然MFCLite存在一些不足﹐但是瑕不掩玉﹐我覺得它對C++和MFC的學習都有非常非常大的幫助。老師的每一本書(無論是譯作還是著作)都是相關領域的精品﹐凡是在市面上見得到的我都買了下來﹐給我帶來的極大的幫助。現在我正在急切地等待老師的《STL原碼剖析》﹑《泛型設計與STL》﹑《標準C++程式庫》等書籍。希望老師注意身體﹐給我們寫出更好的作品﹗



Hi, xioax, 你好:

以下回答你的一些疑問與建議。

> 1﹑對象持久性。由於兩年前我曾仔細研究過Turbo Vision的持久性﹐對這個問題比較熟悉。所以一開始我就發現了您的代碼中所存在的問題﹐下面是我的測試程序(部份)﹐還有輸出文件的內容﹕…

> 我覺得對於C++ 對象持久性而言﹐最重要的問題﹕一個是如何保存相關的類信息﹐另一個就是如何解決上述問題﹗在您的兩本著作《多形與虛擬》﹑《深入淺出MFC》中對前者都有很精闢的論述﹐唯獨後者一點也沒有提及﹐不能不說是一個很大的瑕疵。對於如何解決這個問題也不是很困難﹐只要先實現CMapPtrtoPtr和CPtrArray﹐在寫入時先查map如果已寫過﹐就把輸出序號寫入﹐如果沒有就把對象的地址和輸出序號插入map﹐再把數據寫入文件。讀出時﹐遇到第二種情況﹐就先創建一個對象把數據讀出﹐接著再把新建對象的地址加到數組尾端﹐遇到第一種情況﹐就以輸出序號為索引直接從數組中得到對象(由於寫入和讀出的順序一樣﹐僅用輸出序號就可以完全解決問題)。


●侯捷回覆:你所指出的,對我是當頭棒喝。的確,模擬Persistence時我很少考慮你所說的(alias)情況。針對你的提議,我已修正 MFCLite3並開放於本網頁

MFC(Lite) 不但考慮了你所說的那種(alias)情況,還對「隸屬相同class」的不同objects的文件讀寫做了優化考量。對於已儲存 class information(例如class name和 schema no.)的 class而言,再儲存一次是一種浪費 — 浪費空間也浪費時間。因此,MFC的 CArchive利用CMapPtrToPtr做為cache,不但用來安置CObject*,也用來安置CRuntimeClass*。讀取文件時,道理相同,使用CPtrArray,不但安置CObject*也安置CRuntimeClass*。我不打算在MFCLite中再多加上CMapPtrToPtr和CPtrArray的實作(那恐怕又多出1000行代碼,而且CMapPtrToPtr是以 hash table完成,那就使閱讀的門檻又高了些),我已在新版的 MFCLite 3.0 中以C++ 標準程式庫的 map和 vector 取而代之。

MFCLite的閱讀門檻愈壘愈高,樂了諸位功底深厚的人,可難為了其他功力尚淺的讀者呀。對我而言,取捨成了難事。

我保留並擴大了你的測試,放在 MFCLite Application (mfclapp.cpp) 的 CMyWinApp::InitInstance()中。程式一執行就會在螢幕上顯示這段測試結果並顯示 StoreMap 和? LoadArray,然後才開始一般正常的執行流程。你對persistence 的這段 hint 帶給我莫大樂趣,迫使我完成《深入淺出MFC》第 8 章關於 document format 中的 tags(圖8-10a, 圖8-10b內的 FFFF, 8001, 8003...以及未出現於該圖的 tags 如 0002, 0005...)的深刻體會。


> 2﹑CPtrList的實現有內存泄漏﹐下面是相關代碼和我的修改(紅色)﹕…

●侯捷回覆:修改完畢。我沒有採用你的方式,而是採用MFC的方式。當初實作MFCLite時,為求簡化略去了一些東西,不想造成了memory leak 而不自覺。現補上。我的額頭開始冒汗了。

> 我覺得就MFCLite的目的而言﹐完全沒有必要象MFC那樣實現一些比較高級的內存管理技巧。因為一來這些內存管理與MFC的應用程序架構完全沒有關係﹐二來加大了讀者的閱讀難度﹐三來加大了MFCLite的出錯機會。如果CPtrList使用CObList的實現方法就沒有這麼多問題﹗

●侯捷回覆:CPtrList的精巧設計,與application framework之間非常獨立。雖然它比較複雜,應該不至於混亂讀者對application framework的學習。保留這麼複雜的list實作,有兩個用意,(1) CPtrList是MFC內部自我維護管理時,大量運用的一個data structure,因此對於效率(空間和時間)都很要求 (2)既然其十分獨立,再複雜也不至於混亂讀者對application framework的學習,那麼展示一下這種不錯的設計,滿好的。

> 另外﹐您說在下面的代碼中﹐刪除CPtrList中的元素程序會崩潰﹐不知是不是上面的原因﹖

●侯捷回覆:不是。原因已查出,乃因我把所有的documents的 delete動作放錯位置。這些動作在 MFC 之中應該由 CFrameWnd::OnClose()觸發(目前MFCLite尚未實作之),我卻把它們放在?CMultiDocTemplate::~CMultiDocTemplate()。下面是 MFCLite 的 CallStack:

CMultiDocTemplate::RemoveDocument() // (B)
CDocument::~CDocument()
CMultiDocTemplate::~CMultiDocTemplate() // (A)
CDocManager::~CDocManager()
CWinApp::~CWinApp()

(A) 已針對 pDoc 呼叫 m_docList.RemoveAt(),
(B) 又針對 pDoc 呼叫 m_docList.RemoveAt(),但 pDoc 當時已經不在 m_docList 中了,Find() 傳回 NULL,RemoveAt(NULL) 當然就掛了。

治本之道是模擬 MFC,開發 window close system(也就是你的第5個問題),治標之道則是在 CPtrList::RemoveAt() 一開始增加一行,判斷刪除位置是否為 NULL,如是則立刻回返。

> 3﹑CDocument::OnOpenDocument和CDocument::OnSaveDocument有內存泄漏﹕

●侯捷回覆:應該說是資源洩漏(resource leak)。已全部補妥。我的身上開始冒汗了。修補這個問題的同時,一併修補了 CFile::~CFile() 和 CFile::Close() 內的安全檢驗工作。

> 4﹑我發現CObject的析構函數不是虛擬的。我想一定是老師的一時疏忽。:-)面對幾千行的代碼﹐誰敢保證沒錯呢﹖﹗

●侯捷回覆:全部補妥。我汗流夾背了。

> 5﹑MFCLite對新建文件﹑打開文件和保存文件模擬得很好﹔但是對關閉窗口﹐進而造成文檔的摧毀卻模擬得不夠。其實在C++中由於析構錯誤造成的內存泄漏﹐往往是最常見﹐也是最難排除的Bug。希望在MFCLite的新版本能看到相應的模擬﹗

●侯捷回覆:汗如雨下。對於你所提的問題,我原本模擬了一些,後來覺得有點煩,不在我最關心的主軸範圍內,就放下了。針對你的提議,我可能會在MFCLite3.0中實作出來,也可能在書中描述這些問題,留給讀者去實現。

> 雖然MFCLite存在一些不足﹐但是瑕不掩玉﹐我覺得它對C++和MFC的學習都有非常非常大的幫助。老師的每一本書(無論是譯作還是著作)都是相關領域的精品﹐凡是在市面上見得到的我都買了下來﹐給我帶來的極大的幫助。現在我正在急切地等待老師的《STL原碼剖析》﹑《泛型設計與STL》﹑《標準C++程式庫》等書籍。希望老師注意身體﹐給我們寫出更好的作品﹗

●侯捷回覆:你非常優秀,基本功非常好。浙大電子工程系的學生真是令我刮目相看呀。《STL源碼剖析》和《C++標準程式庫》簡體版出版後各贈你一本,表示我的感謝,以及對你的期許。《泛型程式設計與STL》侯捷譯本只有繁體版,連同《C++ Primer 3e》侯捷譯本(只有繁體版)近日一起寄送給你。請告訴我你的郵寄地址。我只能寄海運(大約需時25天),空運實在太貴了 :)??

你有非常好的代碼追蹤能力,像獵犬一樣的鼻子 :)。我想你肯定用心追蹤過 MFC 源碼。賞析名家手法,是將自己拉抬到制高點的一個重要法門,形同大師灌頂。我自己離開編程第一線後,以此法修練自己,開拓自己。對於你,一個剛畢業的小夥子,我感到十分好奇。(你怎麼知道你的第一個問題的解法?你自行閱讀MFC源碼而參悟的嗎?真是不簡單)

感謝你給我如此豐富的訊息。MFCLite 十分複雜,能夠看懂它又指出問題,切中要害,甚至提出解法,實在很不容易。目前尚未有任何一位讀者給我關於MFCLite 的訊息。我聽說浙大的計算機水準很高,從你的來信得到了一些證明 :)