发信人: muyang008(operator)
整理人: wenbobo(2003-08-23 10:55:55), 站内信件
|
C++ Primer 答客问 (27) 【标准与实作之间】
PC 环境上三种编译器对 C++ Standard 的支援
侯捷 [email protected]
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 之间,
相当混淆而令人迷乱。
■ |
|