作者: 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 )