C++ Primer 答客問 (27) 【標準與實作之間】
PC 環境上三種編譯器對 C++ Standard 的支援
侯捷 jjhou@ccca.nctu.edu.tw
1999.12.29 第一次發表於
清大.楓橋驛站(140.114.87.5).電腦書訊版(Computer/CompBook)
本文將於日後整理於 侯捷網站/答客問 /C++ Primer 中文版
侯捷網站:www.jjhou.com
----------------------------------------------------------------
●C++ Standard 相容編譯器
我想很多人關心,目前市面上哪些廠牌的 C++ 編譯器,完全
支援 C++ Standard。C/C++ User Journal, Nov. 1999 的
【C/C++ Stanadrd FAQ】專欄中,P.J. Plauger 對此問題
的回答是:目前還沒有完全支援 C++ Standard 的編譯器產品。
P.J. Plauger 閱歷廣泛,他的文章提到不同平台上的多種
C++ 編譯器(但並沒有深入談論,都只淺淺帶過)。
回憶歷史,C Standard 定案後,市面上立刻出現一大堆宣稱與標準規格
完全相容 的 C 編譯器。為什麼符合 C++ 標準規格的編譯器卻是如此
難產呢?我想因素之一是,C++ 遠比 C 複雜得多,後期導入的一些
性質(如 member templates, template partial specialization...)
在編譯器技術層面上更是高難度。因素之二是,C++ Standard library
是個浩大的工程,而編譯器通常是以 bundle 的方式搭配其他公司
的 C++ Standard Library,所以彼此的進度、技術、相容性都需要更多
時間來協調配合。因素之三是,C++ 編譯器的價值比較,已經不再只是
單純地比誰對 C++ Standard 的支援程度高(或甚至也不是比較誰的
編譯速度快),而是比較在特定平台上對客戶是否有更多的企業服務。
拿 Windows 環境上的 C++ 編譯器來說,「誰提供更好的 Windows 應用
軟體開發工具與開發資源」可能比「誰更貼近 C++ Standard」,對客戶
而言更為重要。
但是大家還是會很關心哪家編譯器最貼近 C++ Standard -- 即使這
不影響你對 C++ 編譯器的選擇。
●個人經驗
自從我將 L&L(Lippman & Lajoie)的《C++ Primer》譯出後,
面對 C++ Standard 所規範的許多嶄新性質,就有一股躍躍欲試
的衝動。其後由於個人興趣,也因為課程需要,把《C++ Primer 中文版》
整個重新檢閱一遍,並動手在三種不同的編譯器上進行測試
(都是 PC/Windows 平台)。以下整理我的個人經驗,供各位參考。
我儘量為每一個主題列出一份簡短而完整的測試碼。這些或許不是
太嚴謹的測試,但是如果這些符合 C++ Standard 的程式碼無法通過編譯,
我們說這個編譯器未能奉行 C++ Standard,並不過份。但反過來說,
通過我所列之簡易測試者,或許仍有可能在更複雜的情況中出錯(尤其是
template 相關主題)。如果您有相關經驗,歡迎提供出來造福大家。
沒有什麼好點子,可以對以下各個主題編號排序。所以我
以它們出現在《C++ Primer 中文版》上的頁次為序。
擁有英文版的讀者請注意,中文版和英文版頁次完全相同。
●測試環境
我的測試環境是:Intel Pentium,Windows 98,三套 C++ 編譯器:
(1) Microsoft Visual C++ 6.0(以下以 VC6 代表)
(2) Inprise Borland C++Builder 4.0 (以下以 BCB4 代表)
(3) GNU C++ egcs-2.91.57(以下以 G++ 代表)
請注意,GNU C++ 有著各種作業平台上的各種版本。我只用手上的
egcs-2.91.57(for win32) 來測試。
我在 Windows 98 文字模式(console mode)底下進行編譯。以下是
三種編譯器的環境設定(其中路徑可能與你不同。如欲循此方式設定,
請自行修改):
★VC6 環境設定
@echo off
rem
set PATH=C:/MSDEV/VC98/BIN;C:/MSDEV/COMMON/MSDEV98/BIN
set INCLUDE=C:/MSDEV/VC98/INCLUDE
set LIB=C:/MSDEV/VC98/LIB
★BCB4 環境設定
@echo off
rem
set PATH=C:/inprise/CBuilder4/BIN
set INCLUDE=C:/inprise/CBuilder4/INCLUDE
set LIB=C:/inprise/CBuilder4/LIB
★G++ egcs-2.91.57 環境設定
@echo off
rem
set PATH=c:/CYGNUS/CYGWIN~1/H-I586~1/BIN
set INCLUDE=
set LIB=
cls
我的編譯選項(compile options)極其簡單,
★VC6 : cl -GX test.cpp
★BCB4 : bcc32 test.cpp
★G++ : g++ -o test.exe test.cpp
●發表
我把這份內容放在 BBS/News 上,歡迎傳佈,無需特別知會。
歡迎以下的討論與指正:
1. 如果加上任何編譯選項(compile options)即可解決(避免)
所列之任何一個錯誤的話,歡迎(敬請)告知。
2. 如果我的文字或程式碼有任何問題,歡迎(敬請)告知。
3. 如果您有其他(未列於本文)的編譯器問題,歡迎(敬請)告知。
歡迎於 BBS/News 上討論。如能同時轉寄一份給我,以免除我
遺漏觀看的可能,最是感謝。如不欲公開討論,亦歡迎將意見
直接 email 給我。如欲討論,請寫實例,不要只是臆測。
我會註明對本文有助之朋友的大名及其意見,以示感謝。
■C++ Primer p213 下, p.393 下
主題:for loop 的 init-statement 區域內,所有 objects 皆為 local。
測試結果:VC6[x] BCB4[o] G++[o]
實例:
#001 void main()
#002 {
#003 for (int ival=0; ival< 10; ++ival);
#004 for (int ival=0; ival< 10; ++ival);
#005 }
注意:C/C++ User Journal, Oct.1999, p.94 曾提過在 VC 上的一個簡易
閃避辦法,這並且也是明載於 MSDN News(Vol7, Num6, Dr. GUI column)
上的作法。設計一個巨集如下即可解決:
#define for if(0); else for
該文並說,在簡單情況下可以有效運作,但並未測試太過複雜的情況。
■C++ Primer p262 中
主題:STL list 建構時,直接給 list 的大小及初值(做為所有元素的相同初值)
測試結果:VC6[o] BCB4[x] G++[o]
實例:
#001 #include <list>
#002 ...
#003 const int list_size = 64;
#004 list<int> ilist1(list_size); // BCB4 error. VC6 ok. G++ ok
#005 list<string> ilist2(list_size); // BCB4 ok. VC6 ok. G++ ok
#006 list<int> ilist3(list_size, -1); // BCB4 ok. VC6 ok. G++ ok
#007 list<int> ilist4(list_size, 0); // BCB4 error VC6 ok. G++ ok
#008 list<int> ilist5(list_size, 1); // BCB4 error VC6 ok. G++ ok
#009 list<int> ilist6(list_size, -6); // BCB4 ok. VC6 ok. G++ ok
歸納:看來似乎 BCB4 不允許讓 list<int> 只獲得一個引數,也不允許
list<int> 獲得正值初值。很奇怪的行為。我疏忽了什麼嗎?
2001/08/22 補充:
BCB5[o]. 謝謝 Lianchao Xu
■C++ Primer p383 上
主題:透過指向「函式指標陣列」的指標,喚起該陣列中的函式指標,
其型式有簡略式和明顯式兩種。
測試結果:VC6[x] BCB4[x] G++[x] (都不支援簡略型式)
實例:
#001 #include <iostream.h>
#002
#003 typedef int (*PFV)();
#004 int f1() { return 1; }
#005 int f2() { return 2; }
#006
#007 PFV fFuncs[2] = { f1, f2 };
#008 PFV (*pfFuncs)[2] = &fFuncs;
#009
#010 void main()
#011 {
#012 cout << fFuncs[0]() << endl; // 1
#013 cout << fFuncs[1]() << endl; // 2
#014
#015 cout << pfFuncs[0]() << endl; // 簡略式,VC6[x] BCB4[x] G++[x]
#016 cout << ((*pfFuncs)[1])() << endl; // 2 (明顯式)
#017 }
2001/08/22 補充:
Lianchao Xu 建議我把:
#008 PFV (*pfFuncs)[2] = &fFuncs;
改為:
#008 PFV *pfFuncs = fFuncs;
再把:
#015 cout << pfFuncs[0]() << endl; // 簡略式,VC6[x] BCB4[x] G++[x]
#016 cout << ((*pfFuncs)[1])() << endl; // 2 (明顯式)
改為:
#015 cout << (*pfFuncs[0])() << endl; // 1
#016 cout << (*pfFuncs[1])() << endl; // 2
測試結果:VC6[o] BCB4[o] G++[o]
謝謝。不過這並不是我的測試初衷。
■C++ Primer p410 上
主題:各編譯器對 auto_ptr 的支援
測試結果:VC6[o] BCB4[o] G++[x]
實例:
#001 #include <memory> // for auto_ptr
#002 using namespace std;
#003 int main()
#004 {
#005 auto_ptr<int> pi(new int(1024)); // G++ error: auto_ptr undeclared.
#006 }
讀者來函:我測試發現 vc6 的「語意」是錯的,它指派給另一個,會造成
兩個 auto_ptr 都指向同一個物件。BCB5 的結果就正確。
■C++ Primer p411
主題:string* 的建構(直接指定以另一個 string*)
測試結果:VC6[x] BCB4[o] G++[o]
實例:
#001 #include <string>
#002 using namespace std;
#003 int main()
#004 {
#005 string *pstr_type = new string( "Brontosaurus" );
#006 string *pstr_type2( pstr_type ); // <== VC6 error.
#007 delete pstr_type;
#008 delete pstr_type2;
#009 }
■p.412 中下
主題:auto_ptr 的 reset() 動作
測試結果:VC6[x] BCB4[o] G++[x]
實例:
#001 #include <memory> // for auto_ptr
#002 using namespace std;
#003 int main()
#004 {
#005 auto_ptr<int> p_auto_int; // <== G++ error. 見前述 p.410 例
#006 p_auto_int.reset(new int(1024)); // <== VC6 and G++ error
#007 }
■C++ Primer p461
主題:lvalue-to-rvalue 轉換,rvalue-to-lvalue 轉換。
討論:lvalue-to-rvalue 屬於完全吻合(exact match)轉換的一種。
但是 rvalue-to-lvalue 呢?例如以一個 literal constant 或
temporary object 指派給一個 reference?應該不行,除非是
指派給一個 const reference。
測試結果:我的經驗是,各編譯器對此一主題寬緊不一,且無定法(至少我歸納不出)
例一:
#001 int main()
#002 {
#003 int &i = 3; // (1) should be error
#004 // rvalue assign to non-const reference
#005 const int &i2 = 3; // (2) should be ok
#006 // rvalue assign to const reference
#007 int &i3 = int(3); // (3) should be error
#008 // rvalue (temp obj) assign to non-const reference
#009 const int &i4 = int(3); // (4) should be ok
#010 // rvalue (temp obj) assign to const reference
#011 }
#012
#013 // G++ : (1),(3) warning: initialization of non-const reference `int &'
#014 // from rvalue `int'
#015 // jjhou 使用 G++ 2.91.57。
#016 // 據 jyhuang 說,G++ 2.92.2 並不允許通過 (1),(3)。
#017 //
#018 // VC6 : (1),(3) error: 'initializing' : cannot convert from 'const int'
#019 // to 'int &'. A reference that is not to 'const'
#020 // cannot be bound to a non-lvalue
#021 //
#022 // BCB4: (1),(2),(3),(4) warning: Temporary used to initialize 'i'
#023 // in function main ()
例二:
#001 void func1(int i) { }; // pass by value
#002 void func2(int& i) { }; // pass by reference
#003 void func3(int* i) { }; // pass by pointer
#004 void func4(const int& i) { }; // pass by reference
#005
#006 void main()
#007 {
#008 int i;
#009 const int ci = 5;
#010
#011 func1(i); // lvalue-to-rvalue, always ok.
#012 func1(ci);
#013 func2(i);
#014 func2(ci); // (15)
#015 func3(&i);
#016 func3(&ci); // (17)
#017 func4(i);
#018 func4(ci);
#019
#020 func2(int(6)); // (21), rvalue-to-nonconst-reference.
#021 func4(int(6)); // rvalue-to-const-reference, always ok.
#022 }
#023
#024 /*
#025 VC6 :
#026 (15) : error C2664: 'func2' : cannot convert parameter 1 from
#027 'const int' to 'int &'. Conversion loses qualifiers
#028 (17) : error C2664: 'func3' : cannot convert parameter 1 from
#029 'const int *' to 'int *'. Conversion loses qualifiers
#030 (21) : error C2664: 'func2' : cannot convert parameter 1 from
#031 'int' to 'int &'.
#032 A reference that is not to 'const' cannot be bound to a non-lvalue
#033
#034 BCB4 :
#035 Warning (15): Temporary used for parameter 'i' in call to 'func2(int &)'
#036 Error (17): Cannot convert 'const int *' to 'int *'
#037 Error (17): Type mismatch in parameter 'i' in call to 'func3(int*)'
#038 Warning (21): Temporary used for parameter 'i' in call to 'func2(int &)'
#039
#040 G++ :
#041 (15): warning: conversion from `const int' to `int &' discards const
#042 (3) : warning: in passing argument 1 of `func2(int &)'
#043 (17): warning: passing `const int *' as argument 1 of `func3(int *)' discards const
#044 (21): warning: initialization of non-const reference `int &' from rvalue `int'
#045 (3) : warning: in passing argument 1 of `func2(int &)'
#046 */
例三:詳見「C++ Primer 答客問 (19) part-3」
■p.492, p.499, p.500
主題:以 template nontype parameter 做為陣列尺度(dimension)
測試結果:VC6[x] BCB4[o] G++[o]
注意:G++ 對於型別的 const-ness 檢驗極嚴格。以下 (1) 必須改為
const int ia[5] =...; 才能通過 G++。
實例:
#001 template <class Type, int size>
#002 Type min( const Type (&r_array)[size] ) // VC6 error C2057:
#003 { /* ... */ } // expected constant expression
#004
#005 void main()
#006 {
#007 int ia[5]={40,20,49,17,28}; // (1) 注意,G++ 要求需為 const int ia[5]。
#008 min(ia);
#009 }
■C++ Primer p500 中上
主題:利用轉型運算子,將 template function 在模稜兩可(ambiguous)的環境下
以某特定型別具現化(instantiated)。
測試結果:VC6[x] BCB4[x] G++[x]
實例:
#001 template <typename Type, int size>
#002 Type min( Type (&r_array)[size] ) { /*... */ } // VC6 error C2057
#003
#004 typedef int (&rai)[10]; // rai:"10 個 ints 組成之陣列" 的 reference.
#005 typedef double (&rad)[20]; // rad:"20 個 doubles 組成之陣列" 的 reference
#006
#007 // overloaded functions
#008 void func( int (*)(rai) ) { }; // int(*)(rai) 是函式指標型別,
#009 // 該函式的參數型別是 rai。
#010 void func( double (*)(rad) ) { }; // double(*)(rad) 是函式指標型別,
#011 // 該函式的參數型別是 rad。
#012
#013 void main()
#014 {
#015 func(static_cast<double(*)(rad)>(&min)); // (1) 此行無法編譯
#016 // BCB4 E2335: Overloaded 'min' ambiguous in this context
#017 // G++: undefined reference to `func(double (*)(double (&)[19]))'
#018 }
解決之道:繞個道,就可以。將上述 (1):
func(static_cast<double(*)(rad)>(&min));
改為以下即可:
double(*fp)(rad) = &min; // instantiate 'min', using specified type.
func(fp);
■C++ Primer p503
主題:如果 template function 的函式參數型別是一個 class template,
而實際引數是一個 class,此 class 有個 base class,係從「被指定
做為函式參數」之 class template 身上具現出來,那麼 template 的
引數推導可以順利進行。
測試結果:VC6[x] BCB4[x] G++[x]
實例:
#001 template <class T>
#002 class Array { };
#003
#004 template <class T>
#005 class ArrayRC : public Array<T> { };
#006
#007 template <class T>
#008 T min4(Array<T>& array) { return T(0); }
#009
#010 void main()
#011 {
#012 ArrayRC<int> ia_rc();
#013
#014 // min4() 的函式引數型別是 ArraryRC<int>,其 base class 為 Array<int>,
#015 // 正是 function template min4() 的函式參數型別 Array<T> 的
#016 // 一個具現體(instantiation),所以 min4() 應該可以接受它(書上說可以)
#017 min4(ia_rc); // error in VC6, BCB4, G++2.51.97
#018 }
註:2003/01/04 讀者來函,指出將 #012 改為 ArrayRC<int> ia_rc; 即可。
經測試,正確。感謝 royal。
■C++ Primer p507
主題:明白指定 function template 的部份引數的型別,另一部份由編譯器推導而得。
測試結果:VC6[x] BCB4[x] G++[o]
實例:
#001 template <class T1, class T2, class T3>
#002 T1 sum( T2 v2, T3 v3)
#003 { return T1(v2+v3); }
#004
#005 typedef unsigned int ui_type;
#006
#007 ui_type calc( char ch, ui_type ui )
#008 {
#009 // 以下明白指定 T1 為 ui_type,
#010 // T2 被編譯器推導為 char,T3 被推導為 ui_type。
#011 ui_type (*pf)( char, ui_type ) = &sum< ui_type >;
#012
#013 ui_type loc = (*pf)(ch, ui);
#014 return loc;
#015 }
#016
#017 void main()
#018 {
#019 calc('c', ui_type(1024));
#020 }
■C++ Primer p508
主題:明白指定 function template 引數型別
測試結果:VC6[x] BCB4[x] G++[o]
實例:
#001 template <class T1, class T2, class T3>
#002 T1 sum( T2 op1, T3 op2 ) { /* ... */ return T1(10); }
#003
#004 void manipulate( int (*pf)( int,char ) ) { };
#005 void manipulate( double (*pf)( float,float ) ) { };
#006
#007 void main( )
#008 {
#009 manipulate( &sum< double, float, float > );
#010 }
■C++ Primer p511
主題:separate compilation model for function template
測試結果:VC6[x] BCB4[x] G++[x]
VC6 不支援 export template
BCB4 支援關鍵字 export,但 linking 時找不到
temlate instantiation 在哪裡(unresolved external...)
G++ 不支援 export template
■C++ Primer p514
主題:funtion template explicit specialization
注意:書中以 max 為自定之 function template 的名稱。然而有些編譯器已內附
max 函式(有的是屬於 runtime function,有的是屬於 generic algorithms),
切莫以為沒有含入相應的 header file,就不會喚起編譯器內附的東西,
因為有的 header files 會再含入其他 header files,那是表面看不出來的。
所以,自己的碼千萬不要命名為 max/min,才不會混淆自己。
測試結果:VC6, BCB4, G++ 都支援 funtion template explicit specialization。
然而在 char*, const char*, const char[], text literal 之間,
相當混淆而令人迷亂。
■C++ Primer p516
主題:funtion template explicit specialization + argument deduction
測試結果:VC6 表現太寬鬆,不嚴謹。
實例:
#001 #include <iostream>
#002 using namespace std;
#003
#004 template <class T1, class T2, class T3>
#005 T1 sum(T2 op1, T3 op2)
#006 {
#007 cout << "generic form" << endl;
#008 return static_cast<T1>(op1+op2);
#009 }
#010
#011 template<> double sum(float, float);
#012 //上一行在 VC6 竟然可以通過,差勁。
#013 //上一行在 bcb4 出現 e2423: explicit specialization or instantiation
#014 // of non-existing template 'sum'
#015 //上一行在 G++ 出現 : template-id `sum<>' for `sum<>(float, float)'
#016 // does not match any template decaration
#017
#018 // T1 明白指定為 double, T2 推導為 float, T3 推導為 float
#019 template<> double sum<double>(float op1, float op2)
#020 {
#021 cout << "specialization form1" << endl;
#022 return static_cast<double>(op1+op2);
#023 }
#024
#025 // T1, T2, T3 明白指定為 int, char, char
#026 template<> int sum<int, char, char>(char op1, char op2)
#027 {
#028 cout << "specialization form2" << endl;
#029 return static_cast<int>(op1+op2);
#030 }
#031
#032 void main()
#033 {
#034 int i=5;
#035 char c='a';
#036 float f=4.5;
#037 double d=6.5;
#038
#039 cout << sum<int>(i, i) << endl; // generic form 10
#040 cout << sum<double>(f, f) << endl; // specialization form1 9
#041 cout << sum<int>(c, c) << endl; // specialization form2 194
#042 }
■C++ Primer p554
主題:function try block
測試結果:VC6[x] BCB4[x] G++[o]
實例:
#001 #include <iostream>
#002 using namespace std;
#003
#004 class popOnEmpty { /* ... */ };
#005 class pushOnFull { /* ... */ };
#006
#007 int main()
#008 try {
#009 throw popOnEmpty();
#010 throw pushOnFull();
#011 return 0;
#012 }
#013 catch ( pushOnFull ) {
#014 cout << "catch pushOnFull" << endl;
#015 }
#016 catch ( popOnEmpty ) {
#017 cout << "catch popOnEmpty" << endl; // <-- 執行結果:此行。
#018 }
■C++ Primer p564
主題:exception specification
測試結果:BCB4 表現佳,G++ 尚可,VC6 粗糙
實例:
#001 #include <iostream>
#002 using namespace std;
#003
#004 class popOnEmpty { /* ... */ };
#005 class pushOnFull { /* ... */ };
#006
#007 void func1() throw (pushOnFull);
#008
#009 void func1() throw (pushOnFull)
#010 {
#011 throw popOnEmpty(); // BCB4 warning: Throw expression violates
#012 // exception specification in function
#013 // func1() throw(pushOnFull)
#014 // VC6 : no error, no warning
#015 // G++ : no error, no warning
#016
#017 throw pushOnFull(); // BCB4 Warning : Unreachable code in function
#018 // func1() throw(pushOnFull)
#019 // VC6 : no error, no warning
#020 // G++ : no error, no warning
#021 }
#022
#023 int main()
#024 {
#025 try {
#026 func1();
#027 return 0;
#028 }
#029 catch ( pushOnFull ) {
#030 cout << "catch pushOnFull" << endl;
#031 }
#032 catch ( popOnEmpty ) {
#033 cout << "catch popOnEmpty" << endl;
#034 }
#035 }
#036 // 執行結果:
#037 // BCB4: Abnormal program termination
#038 // G++ : none(我想是喚起了 C++ standard library function unexpected(),
#039 // 後者喚起 terminate(),其內喚起 abort()。
#040 // VC6 : catch popOnEmpty(我認為 VC6 對於 exception spec. 的處理太粗糙)
■C++ Primer p643 中
主題:直接在 class 內針對 static const integral data member 給予初值
(所謂 in-class initialization)
測試結果:VC6[x] BCB4[o] G++[o]
實例:
#001 #include <iostream.h>
#002
#003 class Account {
#004 public:
#005 static const int namesize = 16; // <== in-class initialization
#006 };
#007
#008 const int Account::namesize;
#009
#010 void main()
#011 {
#012 cout << Account::namesize << endl;
#013 }
■C++ Primer p834
主題:class templates 內的 friend function
測試結果:VC6[x] BCB4[x] G++[o]
參考:請見稍後 ■C++ Primer p1090 對於 "VC6 的 friend functions" 的深入說明。
實例:
#001 #include <iostream>
#002 using namespace std;
#003
#004 // 以下的 forward declaration 非常重要,見 p834 L-8
#005 template <typename T> class A;
#006 template <typename T> ostream& operator<< (ostream& os, const A<T>& a);
#007
#008 template <typename T> class B;
#009 template <typename T> ostream& operator<< (ostream& os, const B<T>& b);
#010
#011 // 以下以 class A 和 class B 模擬 class Queue 和 class QueueItem 之間的關係
#012
#013 template <typename T>
#014 class A
#015 {
#016 // 以下的 <T> 非常重要,見 p834 L-6
#017 friend ostream& operator<< <T>(ostream& os, const A<T>& a);
#018 public:
#019 A (T i) : _i(i) {
#020 front = new B<T>(i);
#021 back = new B<T>(i);
#022 }
#023 // 為求完整,應再設計 dtor 以避免 memory leak.
#024
#025 private:
#026 T _i;
#027 B<T>* front;
#028 B<T>* back;
#029 };
#030
#031 template <typename T>
#032 ostream& operator<< (ostream& os, const A<T>& a)
#033 {
#034 os << '<' ;
#035 os << *(a.front) << ' ';
#036 os << *(a.back) << ' ' ;
#037 os << '>' << endl;
#038 return os;
#039 }
#040
#041 template <typename T>
#042 class B
#043 {
#044 friend ostream& operator<< <T>(ostream& os, const B<T>& b);
#045 public:
#046 B (T i) : _item(i) { }
#047 private:
#048 T _item;
#049 };
#050
#051 template <typename T>
#052 ostream& operator<< (ostream& os, const B<T>& b)
#053 {
#054 os << b._item; // BCB4 error: _item is not accessible. why?
#055 return os;
#056 }
#057
#058 void main()
#059 {
#060 A<int> a1(5);
#061 A<float> a2(5.4);
#062 A<char> a3('a');
#063
#064 cout << a1 << a2 << a3 << endl;
#065 /* output :
#066 <5 5 >
#067 <5.4 5.4 >
#068 <a a >
#069 */
#070 }
■C++ Primer p842
主題:class templates(內含 nest types)的 friend functions。
測試結果:VC6[x] BCB4[x] G++[o]
實例:
#001 #include <iostream>
#002 using namespace std;
#003
#004 // 以下的 forward declaration 非常重要,見 p834 L-8
#005 template <typename T> class A;
#006 template <typename T> ostream& operator<< (ostream& os, const A<T>& a);
#007
#008 // 以下以 class A 和 class B 模擬 class Queue 和 class QueueItem 之間的關係
#009
#010 template <typename T>
#011 class A
#012 {
#013 // 以下的 <T> 非常重要,見 p834 L-6
#014 friend ostream& operator<< <T>(ostream& os, const A<T>& a);
#015
#016 private:
#017 class B // nested class
#018 {
#019 public:
#020 B (T i) : _item(i) { }
#021 T _item;
#022 };
#023
#024 public:
#025 A (T i) : _i(i) {
#026 front = new B<T>(i);
#027 back = new B<T>(i);
#028 }
#029 // 為求完整,應再設計 dtor 以避免 memory leak.
#030
#031 private:
#032 T _i;
#033 B<T>* front;
#034 B<T>* back;
#035 };
#036
#037 template <typename T>
#038 ostream& operator<< (ostream& os, const A<T>& a)
#039 {
#040 os << '<' ;
#041 os << (a.front)->_item << ' ';
#042 os << (a.back)->_item << ' ' ;
#043 os << '>' << endl;
#044
#045 return os;
#046 }
#047
#048 void main()
#049 {
#050 A<int> a1(5);
#051 A<float> a2(5.4);
#052 A<char> a3('a');
#053
#054 cout << a1 << a2 << a3 << endl;
#055 /* output :
#056 <5 5 >
#057 <5.4 5.4 >
#058 <a a >
#059 */
#060 }
■C++ Primer p844
主題:member templates
測試結果:VC6[o] BCB4[o] G++[o]
實例:
#001 #include <iostream>
#002 #include <string>
#003 #include <vector>
#004 using namespace std;
#005
#006 template <class T>
#007 class Queue {
#008 public:
#009 // class member template
#010 template <class Type>
#011 class CL
#012 {
#013 Type member;
#014 T mem;
#015 };
#016 public:
#017 // function member template
#018 template <class Iter>
#019 void assign( Iter first, Iter last )
#020 {
#021 cout << "Queue<T>::assign()" << endl;
#022 }
#023 };
#024
#025 void main()
#026 {
#027 // nested types
#028 Queue<int>::CL<char> c;
#029 Queue<int>::CL<string> s;
#030
#031 // instantiation of Queue<int>
#032 Queue<int> qi;
#033
#034 // instantiation of Queue<int>::assign( int *, int * )
#035 int ai[4] = { 0, 3, 6, 9 };
#036 qi.assign(ai, ai+4); // output: Queue<T>::assign()
#037
#038 // instantiation of Queue<int>::assign( vector<int>::iterator,
#039 // vector<int>::iterator)
#040 vector<int> vi(ai, ai+4);
#041 qi.assign(vi.begin(), vi.end()); // output: Queue<T>::assign()
#042 }
■C++ Primer p853
主題:separate compilation model for class template
推論:既然 BCB4 and G++ and VC6 都未能支援 separate compilation model for
function templates,我想它們也一定都沒有支援 separate compilation model
for class templates。但我未做測試(挺煩人 :))
■C++ Primer p856
主題:class template specializations
測試結果:VC6[o] BCB4[o] G++[o]
■C++ Primer p861
主題:class template partial specializations
測試結果:VC6[x] BCB4[o] G++[o]
實例:
#001 #include <iostream>
#002 using namespace std;
#003
#004 // form 1
#005 template <class T, int hi, int wid>
#006 class Screen {
#007 public:
#008 void print() { cout << hi << ' ' << wid << " form1" << endl; }
#009 };
#010
#011 // form 2 (template partial specialization)
#012 template <class T, int hi>
#013 class Screen <T, hi, 80> {
#014 public:
#015 void print() { cout << hi << " form2" << endl; }
#016 }; // VC6 error C2989
#017
#018 // form 3
#019 template <class T, int hi>
#020 class Screen <T*, hi, 25> {
#021 public:
#022 void print() { cout << hi << ' ' << sizeof(T*) << ' '
#023 << sizeof(T) << " form3" << endl; }
#024 };
#025
#026 int main()
#027 {
#028 Screen<int, 100, 40> s1;
#029 Screen<int, 100, 80> s2;
#030 Screen<int, 500, 25> s3;
#031 Screen<char*, 300, 25> s4;
#032 Screen<double*, 400, 25> s5;
#033
#034 s1.print(); // output: 100 40 form1
#035 s2.print(); // output: 100 form2
#036 s3.print(); // output: 500 25 form1
#037 s4.print(); // output: 300 4 1 form3
#038 s5.print(); // output: 400 4 8 form3
#039 return 0;
#040 }
■C++ Primer p904
主題:using declaration 可將 base class 內任何一個具名的 member
(條件是 accessible)放進 derived class scope 內。
測試結果:VC6[x] BCB4[o] G++[x]
但如果將下例的 using declaration 移到 Shy::mumble() 之前,則 VC6[o]
實例:
#001 #include <iostream>
#002 #include <string>
#003 using namespace std;
#004
#005 class Diffident {
#006 public:
#007 void mumble (int softnes)
#008 { cout << "Diffident::mumble" << endl; };
#009 };
#010
#011 class Shy : public Diffident {
#012 public:
#013 void mumble(string whatYaSay)
#014 { cout << "Shy::mumble" << endl; };
#015 using Diffident::mumble;
#016 };
#017
#018 void main()
#019 {
#020 Diffident d;
#021 Shy s;
#022 string str("jjhou");
#023
#024 d.mumble(5); // Diffident::mumble
#025 s.mumble(5); // should be "Diffident::mumble"
#026 s.mumble(str); // Shy::mumble
#027 }
■C++ Primer p940
主題:設計 "virtual" new operator(亦即 clone)時所需
的一個技術:如果虛擬函式的 base instance 傳回
'A' class type(或為指標,或為 reference),那麼
虛擬函式的 base instance 可以傳回 'A' type 或 'A'
的 publicly derived class(或為指標,或為 reference)
測試結果:VC6[x] BCB4[o] G++[o]
實例:
#001 class Query {
#002 public:
#003 virtual Query* clone() = 0;
#004 };
#005
#006 class NameQuery : public Query {
#007 public:
#008 virtual NameQuery* clone() { return new NameQuery(*this); }
#009 // VC6 error C2555: 'NameQuery::clone' : overriding virtual function
#010 // differs from 'Query::clone' only by return type or calling convention
#011 // see declaration of 'Query'
#012 };
#013
#014 void main()
#015 {
#016 Query* pq = new NameQuery();
#017 Query* pq2 = pq->clone();
#018
#019 NameQuery* pnq = new NameQuery();
#020 NameQuery* pnq2 = pnq->clone();
#021 }
■C++ Primer p998
註:2001.08.22 新增
主題:虛擬繼承中的 base class ctor invocation 問題。
測試結果:
實例:gcc291[o], vc6[x], cb4[x], cb5[x]
#0001 // gcc[o], vc6[x], cb4[x], cb5[x]
#0002
#0003 #include <iostream>
#0004 #include <string>
#0005 using namespace std;
#0006
#0007 class ZooAnimal {
#0008 public:
#0009 ZooAnimal( string name, bool onExhibit, string fam_name )
#0010 : _name( name ),
#0011 _onExhibit( onExhibit),
#0012 _fam_name( fam_name )
#0013 {}
#0014
#0015 virtual ~ZooAnimal() { };
#0016 string name() const { return _name; };
#0017 string family_name() const { return _fam_name; }
#0018 // ...
#0019
#0020 protected:
#0021 bool _onExhibit;
#0022 string _name;
#0023 string _fam_name;
#0024 // ...
#0025 };
#0026
#0027 class Bear : public virtual ZooAnimal {
#0028 public:
#0029 enum DanceType {
#0030 two_left_feet, macarena, fandango, waltz };
#0031
#0032 Bear( string name, bool onExhibit=true )
#0033 : ZooAnimal( name, onExhibit, "Bear" ),
#0034 _dance( two_left_feet )
#0035 {}
#0036
#0037 void dance( DanceType );
#0038 // ...
#0039
#0040 protected:
#0041 DanceType _dance;
#0042 // ...
#0043 };
#0044
#0045
#0046 class Raccoon : public virtual ZooAnimal {
#0047 public:
#0048 Raccoon( string name, bool onExhibit=true )
#0049 : ZooAnimal( name, onExhibit, "Raccoon" ),
#0050 _pettable( false )
#0051 {}
#0052
#0053 bool pettable() const { return _pettable; }
#0054 void pettable( bool petval ) { _pettable = petval; }
#0055 // ...
#0056
#0057 protected:
#0058 bool _pettable;
#0059 // ...
#0060 };
#0061
#0062
#0063 class Panda : public Bear,
#0064 public Raccoon {
#0065 public:
#0066 Panda( string name, bool onExhibit=true );
#0067 bool sleeping() const { return _sleeping; }
#0068 void sleeping( bool newval ) { _sleeping = newval; }
#0069 // ...
#0070
#0071 protected:
#0072 bool _sleeping;
#0073 // ...
#0074 };
#0075
#0076 Panda::Panda( string name, bool onExhibit=true )
#0077 : ZooAnimal( name, onExhibit, "Panda" ), // <<-- note
#0078 Bear( name, onExhibit ),
#0079 Raccoon( name, onExhibit ),
#0080 _sleeping( false )
#0081 {}
#0082
#0083
#0084 int main()
#0085 {
#0086 Bear winnie("Pooh");
#0087 Raccoon meeko("meeko");
#0088 Panda yolo("yolo");
#0089 }
注意:如果將 #0076 的 =true 去除,則 gcc291[o], vc6[o], cb4[o], cb5[o]
■C++ Primer p1090
主題:friend operator<<
測試結果:VC6[x] BCB4[o] G++[o]
注意:如果使用 <iostream.h> 而不是 <iostream>,在 VC6 中
運用 friend 就比較沒有問題。但如果這麼做的話,由於下例
用到 <string>,一定得 using namespace std; 而這在 VC6 中似乎
導至暗中含入 <iostream>,於是與 <iostream.h> 起衝突。總之,
挖東補西,很煩。VC6 在這主題上表現不佳。
實例:
#001 #include <iostream>
#002 #include <string>
#003 using namespace std;
#004
#005 class WordCount {
#006 friend ostream& operator<<(ostream&, const WordCount&);
#007 public:
#008 WordCount( string& word, int cnt=1 )
#009 : _word(word), _occurs(cnt)
#010 { };
#011 private:
#012 string _word;
#013 int _occurs;
#014 };
#015
#016 ostream& operator <<( ostream& os, const WordCount& wd )
#017 { // format: <occurs> word
#018 os << "< " << wd._occurs << " > " << wd._word; // VC error!
#019 return os;
#020 }
#021
#022 void main()
#023 {
#024 string s1("Hello");
#025 string s2("jjhou");
#026 WordCount w1(s1, 5);
#027 WordCount w2(s2, 7);
#028 cout << w1 << endl; // < 5 > Hello
#029 cout << w2 << endl; // < 7 > jjhou
#030 }
■C++ Primer p1126 下
主題:list<T> 的 object initialization
測試結果:VC6[o] BCB4[x](有瑕疵) G++[o]
實例:
以下這行可通過 VC6 和 G++
list<int> ilist_result(ilist.size());
為了在 BCB4 中通過,需改為:
int i = ilist.size();
list<int> ilist_result(i, -1);
■C++ Primer p1156~p1157
主題:generic algorithms max() 和 min()
測試結果:VC6 所附的 STL 竟未支援 max 和 min 這兩個 generic algorithms。
聯想:試看這個程式:
(01) #include <iostream>
(02) #include <string>
(03) using namespace std;
(04)
(05) template <typename T>
(06) T min( T v1, T v2) // <-- note
(07) {
(08) return (v1 < v2 ? v1 : v2);
(09) }
(10)
(11) class Rect {
(12) friend ostream& operator<<(ostream& os, Rect& r);
(13) public:
(14) Rect(int i) : _i(i) { }
(15) bool operator<(Rect& rhs)
(16) { return (this->_i < rhs._i ? true : false); }
(17) private:
(18) int _i;
(19) };
(20)
(21) ostream& operator<<(ostream& os, Rect& r)
(22) {
(23) os << r._i;
(24) return os;
(25) }
(26)
(27) void main()
(28) {
(29) cout << min( 17, 15) << endl; // 15
(30) cout << min(13.5, 13.57) << endl; // 13.5
(31) cout << min('a', 'e') << endl; // a
(32) cout << min("jjhou", "allan") << endl; // allan
(33)
(34) Rect r1(6), r2(3), r3(9);
(35) cout << r1 << r2 << r3 << endl; // 639
(36) cout << min(r1, r2) << endl; // 3
(37) }
檢討:
一 此程式在 VC6 中失敗,但如果改為 #include<iostream.h>
並移除 using namespace std; 則成功,這是 VC6 的 bug!
(見先前對 friend 的討論,C++ Primer p1090)
二 此程式在 BCB4 中失敗,錯誤訊息如下:
Error E2094 T1.CPP 36: 'operator<<' not implemented in type
'ostream' for arguments of type 'Rect' in function main()
這是因為我們的 template function min() 和 BCB4 提供之 STL 中
的 gemeric algorithm min() 名稱一樣。程式中喚起的其實是 STL 的
gemeric algorithm min(),而其 parameter list 內對於 parameters
的 constness 的要求,導至編譯器在進行 template argument deduction 時,
不接受我所提供的 Rect。只要將程式中的 min() 改為 mymin(),並將
(12) 和 (21) 的最後一個參數型別改為 const Rect&,即可。
注意:有時候雖然你並未明白含入某些 header files,或是你並未明白
使用 using namespace std; 編譯器卻會暗自加上。前者是由於 header files
一個含入一個…導至,後者請看 BCB4 的每一個 header files 的最後面,
幾乎都有這一行:
#ifndef __USING_STD_NAMES__
using namespace std;
#endif
三 此程式在 G++ 中獲得警告訊息如下:
t1.cpp:36: warning: initialization of non-const reference
'class Rect &' from rvalue 'Rect'
t1.cpp:22: warning: in passing argument 2 of
'operator <<(ostream &, Rect &)'
其道理與修改方法,和上述二相同:將 min() 改名為 mymin() 即可。
心得:程式中不要出現任何命名與 C++ standard library 內的 components 同名。
■C++ Primer p1169
主題:generic algorithms random_shuffle()
測試結果:G++ 所附的 STL 對 random_shuffle() 的支援有問題
■Effective C++ 2/e p70
主題:如果 base class 的 operator= 係由編譯器產生,亦即所謂
default operator=,某些編譯器會拒絕讓你明白喚起 base class operator=,
這不是好現象。
測試結果:VC6[o] BCB4[x] G++[o] (BCB4 表現不佳,拒絕我們喚起上述 operator=)
實例:
#001 class Base {
#002 ...
#003 // no defined operator=
#004 };
#005
#006 class Derived : public Base {
#007 ...
#008 Derived& operator=(const Derived& rhs);
#009 };
#010
#011 Derived& Derived::operator=(const Derived& rhs)
#012 {
#013 if (this == &rhs) return *this;
#014
#015 // explicitly invoke Base operator=。VC6[o] BCB4[x] G++[o]
#016 Base::operator=(rhs);
#017 ...
#018 }
以下是 2001/03/03 新增:
■C++ Primer p688
主題:local class
測試結果:VC6 不符 C++ Standard
(01) // VC6[x] CB4[x] G++[x]
(02)
(03) int a, val;
(04)
(05) void foo( int val )
(06) {
(07) static int si;
(08) enum Loc { a = 1024, b };
(09) class Bar {
(10) public:
(11) Loc locVal; // ok;
(12) int barVal;
(13) void fooBar( Loc l = a ) { // ok: Loc::a
(14) barVal = val; // error: local object
(15) barVal = ::val; // ok: global object
(16) barVal = si; // ok: static local object
(17) locVal = b; // ok: enumerator
(18) }
(19) };
(20) // ...
(21) }
(22)
(23) int main()
(24) {
(25) foo(5);
(26) }
編譯結果
[VC6]:
../688.cpp(16) : error C2065: 'si' : undeclared identifier
../688.cpp(17) : error C2065: 'b' : undeclared identifier
../688.cpp(17) : error C2440: '=' : cannot convert from 'int' to
'enum foo::Loc'
Conversion to enumeration type requires an explicit cast (static_cast,
C-style cast or function-style cast)
結論:VC6 不符合 C++ standard.
[BCB4]:
Error E2451 ../688.CPP 14: Undefined symbol 'val' in function Bar::fooBar(Loc)
結論:BCB4 符合 C++ standard.
[GCC]:
..//688.cpp: In method `void foo(int)::Bar::fooBar(enum foo(int)::Loc = a)':
..//688.cpp:14: use of parameter from containing function
..//688.cpp:6: `int val' declared here
結論:GCC 符合 C++ standard.
--- the end