精華區beta CompBook 關於我們 聯絡資訊
作者: Xshadow (沒力的混小子) 看板: CompBook 標題: <深度探索 C++ 物件模型> 勘誤、討論、補充 -- 侯俊傑 [代貼] 時間: Sat Oct 24 16:27:24 1998 <深度探索 C++ 物件模型> 勘誤、討論、補充 ■<深度探索 C++ 物件模型> 侯俊傑譯/碁峰/1998 ■原著:<Inside The C++ Object Model> Lippman/Addison Wesley/1996 侯俊傑 1998.09.09 第一次發表 1998.10.24 第二次發表 清大.楓橋驛站(140.114.87.5) CompBook 版, programming 版, oop 版 ------------------------------------------------------------------ 我的譯作 <深度探索 C++ 物件模型> 經各方指正,以及我自己的覆閱, 發現了一些錯誤。這些錯誤,有的是我筆誤,有的是我誤譯,有的是新 發現的原作者 Lippman 錯誤。茲記錄於下。凡能寫碼驗證的,我都寫碼 驗證後,才敢說是 Lippman 原書有誤。 筆誤或誤譯的部份,當於新刷更正。本文最重要的,大約是補充討論 的部份了,此部份無法於新刷中加上(因為這不是 Lippman 原書內容)。 感謝 chilong、黃昕暐先生、林昆穎先生、黃俊達先生、劉東岳先生、 黃俊堯先生、唐志青先生 提供我許多寶貴意見。 ● 在這份豐富的 <討論、勘誤、補充> 文件中,黃俊達先生和劉東岳先生做出 最大貢獻。黃俊達先生是交大電子博士,做的是 IC 設計工作,OO 為其 業餘興趣。四眼王蟲為電腦圍棋高手,出沒於網路 Programming/OOP 版, 為人排憂解難。我很高興因為這本書,結識這兩位熱心人士。一個人願意在 看過一本書之後提筆寫下數十頁意見,與作者(譯者)討論,實在需要絕大 的熱情支持。我雖然被他們操了個半死(也因此我要把手上一些書的 delay 責任算一部份在他們頭上 :)),但真是很感謝兩位的心意。 其他寫信與我的朋友,我也有相同的感謝。 ● 以下是更正內容。其中 Lm 表示第 m 行,L-n 表示倒數第 n 行。 =============================================================== 以下已於二刷書籍中修正。 ■ p.132 L7(Lippman 錯誤) 原文:所得結果加上 z 的偏移值(相對於 origin 起始位址),並減 1,就會... 更正:所得結果減去 z 的偏移值(相對於 origin 起始位址),並加 1,就會... 致謝:林昆穎先生 ■ p.145 L-5 (侯俊傑筆誤) 原文:void print { const Point3d& ) { ... } 更正:void print ( const Point3d& ) { ... } ■ p.194 L-11 (Lippman 錯誤) 原文:Abstract_base::mumble_set() 更正:Abstract_base::mumble() 致謝:chilong ■ p.243 中央 (Lippman 錯誤) 原文:_identity 表示檔案中所定義的第一個 nonstatic object。 更正:_identity 表示檔案中所定義的第一個 static object。 致謝:chilong ■ p.314 中央的程式碼(侯俊傑筆誤) 原文:if (typeif (rt) == .......) 更正:if (typeid (rt) == .......) 致謝:chilong =============================================================== 以下為二刷修正稿交出後發現。 ■ p.28 討論 討論:劉東岳先生認為 Lippman 對 word 的看法是 computer science 的 習慣說法,我的譯註中對 word 的看法似乎是 Windows 上才有的說法。 致謝:謝謝提醒。我在譯註中用詞還算委婉,並不獨斷,大概不致引起誤會。 ■ p.39 L6(侯俊傑筆誤) 原文:出現不尋常活動的第一個徵候時 更正:出現不尋常活動的第一個癥候時 ■ p.61 L7(侯俊傑筆誤) 原文:X x3 = x( x0 ); 更正:X x3 = X( x0 ); 致謝:黃俊達先生、黃俊堯先生 ■ p.63 L-3(侯俊傑筆誤) 原文:Stroustrups 更正:Stroustrup 致謝:黃俊堯先生 ■ p.66 中 (侯俊傑誤譯) 原文:以 named return value 取代 result 參數 更正:以 result 參數取代 named return value 致謝:唐志青先生 ■ p.72 L-6 (侯俊傑誤譯) 原文:如果答案是 yes,那麼提供一個 copy constructor 的 explicit inline 函式 實體就非常合理,那將要求你的編譯器提供 NRV 最佳化。 更正:如果答案是 yes,那麼提供一個 copy constructor 的 explicit inline 函式 實體就非常合理 -- 在「你的編譯器提供 NRV 最佳化」的前提下。 ■ p.91 code 中段(Lippman 錯誤) 原文: mumble( length val ) { _val = val; } 更正:void mumble( length val ) { _val = val; } 致謝:劉東岳先生 ■ p119 下, p120 上 (Lippman 錯誤) 原文:Vertex *pv = pv3d; (此行在 p119) Vertex *pv = pv3d ? pv3d->__vbcPoint2d : 0; 修改:Point2d *p2d = pv3d; Point2d *p2d = pv3d ? pv3d->__vbcPoint2d : 0; 致謝:黃俊達先生、劉東岳先生 侯俊傑感想:從 Lippman 的錯誤去推想其原意,真是不容易啊! ■ p122 上 (Lippman 錯誤) 原文:Vertex *pv = pv3d; Vertex *pv = pv3d ? pv3d + pv3d->__vptr__Point3d[-1] : 0; 修改:Point2d *p2d = pv3d; Point2d *p2d = pv3d ? pv3d + pv3d->__vptr__Point3d[-1] : 0; 致謝:黃俊達先生、劉東岳先生 侯俊傑感想:從 Lippman 的錯誤去推想其原意,真是不容易啊! ■ p131 L-5(討論) 原文:為了區分 p1 和 p2,每一個真正的 member offset 值都被加上 1。 討論:BCB, g++, CC 均如此,唯 VC 沒有加 1。 致謝:黃俊堯先生 ■ p.160 L-1 (侯俊傑筆誤) 原文:Derived::close() 更正:Derived::clone() 致謝:黃昕暐先生 ■ p.165 討論 網友 chilong 問:為什麼圖4.2 Deived object 的 Base1 subobject 中的 vptr 所指的 vtbl 之中,#4 slot 竟然指向 Base2::mumble()? 他認為,由於 Base1 class 中並沒有 mumble() 這個虛擬函式,所以 Base1 subobject 中不應該有對應的 slot。 侯俊傑答覆:這是因為經過圖4.2(即 p160 碼)的多重繼承,程式可經由 Derived* 呼叫 Base2::mumble(),而 Derived* 其實指的是 Derived object 中的 Base1 subobject(是一種自然多型),所以 Base1 subobject 的 vtbl 必須有 slot 指向 Base2::mumble() 才可以。這其實就是 p.166 的第二種 情況。圖4.2 無誤! ■ p.181 L7 (討論) 原文:Point3d* (Point3d::*pmf)(const Point3d&) const = &Point3d::cross_product; 討論:第二行最前面不管有沒有 '&',效果都一樣,都是表示函式位址。 ■ p.181 L11 (Lippman 錯誤) 原文:(*pA.pmf)(pB); 更正:(pA.*pmf)(pB); ■ p.181 L-7 (Lippman 錯誤) 原文: ? ( *pmf.faddr )( &pA + pmf.delta ) 更正: ? ( *pmf.faddr )( &pA + pmf.delta, pB ) 致謝:黃俊達先生 ■ p.181 L-5 (討論) 原文: (&pA + pA.__vptr__Point3d[ pmf.index ].delta, pB); 討論: 黃俊達先生認為應改為 (&pA + pA.__vptr__Point3d[ pmf.index ].offset, pB); --- (1) 或是可寫為: (&pA + pmf.delta, pB) --- (2) 侯俊傑同意 (2) 式。至於 (1) 式,參悟中。 ■ p.211 L-13 (侯俊傑誤譯) 原文:而更後繼(第二個 base class 以後)的繼承,則由 PVertex ... 更正:而更往後(往下)的繼承,則由 PVertex ... ■ p.215 L11 (侯俊傑誤譯) 原文:也就是說,我們必須多少對虛擬機制有點認識,才知道是否這個呼叫源自... 更正:也就是說,虛擬機制本身必須知道是否這個呼叫源自... ■ p.217 code (Lippman 錯誤) 原義:code 中段有兩個 vptr 初始化設定動作。 更正:由此例之 class hierarchy 看,恐怕不只兩個 vptr。 致謝:黃俊達先生 ■ p.221 L-14 補充說明 原文:// 譯註:原書有誤,這裡缺少一個 return。 補充說明:上述的 return 動作完整寫法是 return *this; 致謝:黃俊達先生 ■ p.231 5.5 節第一行(侯俊傑誤譯) 原文:如果 class 沒有定義 destructor,那麼只有在 class 內含一個 member 或其 class 擁有 destructor 的情況下,編譯器才會 自動合成出一個來。 更正:如果 class 沒有定義 destructor,那麼只有在 class 內含的 member object (抑或 class 自己的 base class)擁有 destructor 的情況下,編譯器才會 自動合成出一個來。 ■ p.234 L1(侯俊傑誤譯) 原文:1. ...把相關的 virtual table 清除。 更正:1. ...把相關的 virtual table 重新設定(為適當之 base class vtbl)。 致謝:黃俊達先生 ■ p.234 最上(Lippman 錯誤) 原義:dtor 的內部實際動作(含被編譯器附加的動作)如書中之 1,2,3,4,5。 更正:應該是 2,3,1,4,5(才符合 p.233 最下所說的「與 ctor 次序相反」) 致謝:黃俊達先生 ■ p.234 L-10(侯俊傑筆誤) 原文:destructory 更正:destructor 致謝:劉東岳先生 ■ p.237 L-4 (Lippman 錯誤) 原文:operator==( const Y& ) const; 更正:bool operator==( const Y& ) const; 致謝:黃俊達先生 ■ p.238 L5 (侯俊傑筆誤) 原文:// 譯註:convertion 運算子 更正:// 譯註:conversion 運算子 ■ p.250 L5 (侯俊傑誤譯) 原文:如果 Point 既沒有定義一個 constructor 也沒有定義一個 destructor, 那麼我們需要做比「內建(build-in)型別所組成之陣列」更多的工作, 也就是說配置足夠的記憶體以儲存 10 個連續的 Point 元素。 更正:如果 Point 既沒有定義一個 constructor 也沒有定義一個 destructor, 那麼我們的工作不會比建立一個「內建(build-in)型別所組成之陣列」更多, 也就是說我們只要配置足夠記憶體以儲存 10 個連續的 Point 元素即可。 ■ p.262~p.263 討論 原義:如果 Point *ptr = new Point3d[10]; // Point 是 Point3d 的 base class 那麼 delete [] ptr; 只會喚起 Point::~Point() (p.262) 書中(p.263)並說此問題的解決之道在程式員層面,而非語言層面。 討論: C++ Standard Draft CD2 上說,此情況為 undefined。 此亦即 <More Effective C++> item3 : "never treat arrays polymorphically". 不過我們發現,某些編譯器能夠「部份解決」此問題。以下例而言: Point *ptr = new Point3d[2]; // Point3d : public Point , dtor is virtual. delete [] ptr; ◆ VC5 (and VC6) 執行結果: ~Point3d() ~Pointd() ~Point3d() ~Pointd() 而且每個 dtor 都能 access 到正確的 data members。但我們尚不能夠 因為 VC 有這種表現就放心大膽地 "treat array polymorphically", 因為在多重繼承等更複雜的情況中,還是有不正確的情況發生。 ◆ BCB3 執行結果: ~Pointd() ~Pointd() ~Pointd() ~Pointd() 所以在 BCB3 中 "never treat array polymorphically"。 ◆ g++ 結果: segmentation fault (in Pentium/FreeBSD) (or bus error, in Sparc/Solaris) 所以在 g++ 中 "never treat array polymorphically"。 ◆在這個主題上,黃俊達先生、劉東岳先生以及侯俊傑寫了不少有趣的 tiny program 來驗證一些推論以及想法,得到上述結果。其間細節目前沒時間 整理(在一份正式的 document 中以圖形和足夠的篇幅來介紹,才是好作法)。 我們的結論是,既然 C++ Standard 說此情況為 undefined,所以三家編譯器都 沒有錯。不過 VC 做得不錯,走得較遠,解決了部份問題。 當然,請注意,如果直接以上述 ptr 去處理 Point3d[3] 的 public data members, 一定會出錯(因為 object layout 之故);所幸很少情況會把 data members 設計為 public,於是迂迴避免了此種錯誤。 致謝:黃俊達先生、劉東岳先生 提示:可從 <More Effective C++> item 8 獲得一些有用的相關觀念: "Understand the different meanings of new and delete" ■ p.263 L6 (Lippman 錯誤) 原文:錯誤的 constructor,而且自從第一個元素之後,該 constructor 即被 更正:錯誤的 destructor,而且自從第一個元素之後,該 destructor 即被 ■ p.264 L8 (Lippman 錯誤) 原文:Point2w ptw = ( Point2w* ) arena; 更正:Point2w *ptw = ( Point2w* ) arena; ■ p.266 下方程式碼 (討論) 原義:這段碼討論 placement new operator 的一個潛在問題 討論:為獲得 VC5 對 placement new operator 的支援,必須含入 new.h。 但在 BCB3 中則不必含入 new.h。 致謝:劉東岳先生 ■ p.267 L1 (侯俊傑筆誤) 原文:請看本書 3.8 節 更正:請看 C++ Standard 3.8 節 ■ p.267 L-4(侯俊傑筆誤) 原文:避免避免 copy constructor 和... 更正:避免執行 copy constructor 和... ■ p.268 L-8 (Lippman 錯誤) 原文:a.operator+( temp, b ) 更正:temp.operator+( a, b ) 致謝:黃俊達先生 ■ p.277 L9 (侯俊傑筆誤) 原文:clas 更正:class ■ p.289 中下 (侯俊傑筆誤) 原文:_member = foo( _member ); 更正:_member = foo( _val ); 致謝:劉東岳先生 ■ p.310 下 (侯俊傑誤譯) 原文:RTTI 機制提供一個安全的 downcast 設備,但只對那些抑制「多型(也就... 更正:RTTI 機制提供一個安全的 downcast 設備,但只對那些展現「多型(也就... 致謝:劉東岳先生 =============================================================== 黃俊達先生和劉東岳先生(dyliu,四眼的王蟲)給我許多寶貴的意見 和廣泛的討論。以下是整理。 ■主題1:NRV 最佳化 p.67~69 ★NRV 重點整理:當函式以 object 做為 return value 時, 通常會引發 copy constructor。但編譯器可以有比較聰明的 作法,避免引發 copy constructor,是謂之 NRV 最佳化。 這對程式的執行效率可能帶來很大的幫助(在某些情況下)。 ★劉東岳先生詢問:p69 之譯註,是否表示 VC 支援 NRV 最佳化? ★侯俊傑答覆:不!觀察測試結果,第一組數據為 2:21,第二組數據為 2:26。 顯示 10000000 次迴路後,累積差異極微。所以我認為 VC 應該沒有對此例 實施 NRV 最佳化。p.69 譯註中的表格的欄位標題可能引起誤會,請將: 未實施 NRV 實施 NRV 實施 NRV + -O (-O 表示最佳化) ------------------------------------------------------- 改為: 版本 1 版本 2 版本 2 + -O (-O 表示最佳化) ------------------------------------------------------- (版本 1 表示沒有 explicit copy constructor,版本 2 表示 有 explicit copy constructor,如 p.67 最後所言) ★黃俊達先生認為:Lippman 在 p67 最後一行所言『這個程式的第一個版本 不能實施 NRV 最佳化,因為 test class 缺少一個 copy constructor』, 此語錯誤。黃先生認為如果程式沒有 explicit copy constructor,編譯器會 自動為我們做出來(如為 trivial,則直接 bitwise copy;如為 nontrivial, 則由編譯器為我們合成出一個 copy constructor)。因此,有沒有 explicit copy constructor 並不影響 NRV 最佳化的實施。他認為 NRV 最佳化主要是 由編譯器 option 來決定要不要實施。他並且做了一些實驗,判斷 VC 和 gcc 都沒有做到 NRV 最佳化,而其不做的理由不是因為技術上的困難,是為了 避免造成「user defined copy constructor 之副作用失效」-- 所謂副作用 是指,例如「在 user defined copy constructor 中做一個 cout 輸出」之類 這種「與 memberwise copy 無關」的動作。 ★侯俊傑答覆:我同意黃俊達先生的看法。請注意,Lippman 在 p.205 下方, p.221 上方等處,仍再三強調 copy constructor 對於 NRV 最佳化的導引之功,不知是否其間有什麼是我們沒想到的? ★唐志青先生亦來信對於 NRV 提出與黃俊達先生相同的看法。感謝。 ■主題2:假設 B 和 D 分別為 based 和 derived;B 有虛擬函式 vfunc()。 現在 D::vfunc() 改寫 B::vfunc(),請問 D::vfunc() 的 return type 是否一定要和 B::vfunc() 的相同? Lippman 在 p160 所舉例子中,上述兩者的 return type 不相同(但有某種 繼承關係存在)。侯俊傑給了一個譯註,說兩者必須相同,否則過不了 VC 編譯器。 ★劉東岳先生和黃俊達先生都認為,侯俊傑的譯註在過去是對的,現在則是錯的, 因為 C++ Standard 已做了修正。Lippman 的範例程式之所以無法通過 VC, 是因為 VC 在這一點沒有遵循 C++ Standard。此主題可參閱 C++ Standard Draft CD2 的 10.3 [class.virtual],亦可參閱本書 p.166 所載的第三種情況。 黃俊達先生並提示,此與 "virtual ctor" 這個題目有關。 ★侯俊傑答覆:同意,謝謝指正。 ■主題3:assignment operator 之 return type ★黃俊達先生認為,Lippman 在 101,107,109,119 等頁中都把 operator+= 之 return type 定為 void,這是不好的設計觀念。通常有 assignment 動作 之 operator(如 =, +=, -=)應傳回 *this(如本書 p221。見本檔稍早之修改)。 如此才能與 C++ build-in type 之 assignment operator 的習慣行為一致。 此觀念可見 <Effective C++> Item15。 ★侯俊傑答覆:同意。 ■主題4:destructor 之必要性 ★黃俊達先生認為,Lippman 在 p232~p233 對於「dtor 是否必要」的敘述, 有必要再討論。他認為,基本上 base class 無論如何最好都設計一個 virtual dtor -- 即使是個空函式都好。因為如果沒有 virtual dtor, 那麼當 base class "be used polymorphically",一旦我們 delete 其 object, 就會有災難發生(※侯註)。此觀念可見 <Effective C++> Item14。如果我們一開始 沒有定義 virtual dtor,待程式發展到一定程度,也就是 base class "used polymorphically" 的情況發生了,才回頭為 base class 定義一個 virtual dtor,那麼,由於改變了 vtbl 的 layout,導至所有 與此 base class 有關的程式碼,都需要重新編譯。 ※侯註:為什麼會發生災難,可參考 <多型與虛擬> ch1,p59。 ★侯俊傑答覆:同意。至於 base class 是否常常會 "be used polymorphically" 呢?是的,常常。所以,上述觀念十分重要。至於「事後才回頭加上 virtual dtor」,的確會改變 vtbl layout,但這裡衍生出 COM 的一個討論: 「COM interface 如果在尾端加上新的 virtual functions,其新的 COM object(DLL)並不會影響舊程式(與舊的 COM interface 聯結者)的崩潰」。 這個觀念可見 <Essential COM> chap1 "Object Extensibility"。 =============================================================== --- the end -- ※ Origin: 楓橋驛站<bbs.cs.nthu.edu.tw> ◆ From: dial5-169.Eden.nthu.edu.tw ※ X-Info: Mave -> ric.bbs@ptt.csie.ntu.edu.tw ※ X-Sign: 0ROABL1PH.F26LvjOsEk (99/07/09 7:05:05 )