荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: contonazhao (烦恼的松鼠仔), 信区: Visual
标  题: C++ 标准与实作之间(^_______^从侯捷的网站找来的)
发信站: 荔园晨风BBS站 (Sun Jul 13 20:24:18 2003), 站内信件

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



--
做人,就不要做事,做事,就不要做人!

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.40.158]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店