Accelerated C++示例程序在Microsoft Visual C++ 6.0编译环境下已知的问题
时间:2010-10-11 来源:Eric Win
Accelerated C++示例程序在Microsoft Visual C++ 6.0编译环境下已知的问题
文/ Eric
2010-09-28
最近在阅读Andrew Koeing经典的C++入门书Accelerated C++,本科时学校用的是谭老的《C++程序设计》,当时学得云里雾里,挣扎着把C++这门课PASS了就再没碰过。现在因为学习MFC编程,又把谭老的那本红皮C++书翻出来看,结果还是云里雾里,多态看了N遍还是不解,不禁感慨国内科技图书的编写者水平就是高,联系之前选书的经验,觉悟到像我这种菜鸟还是比较适合看老外写的书。GOOGLE了一圈,大牛们普遍推荐这本Accelerated C++,本来想网购,结果发现居然绝版了,Faint~,还好书店有卖的 :)。
读到第3章的时候,编译测试书中的例程,把书上的程序敲进去,编译运行居然有错误。以为是我敲错了,反复查了几遍,还是有错误,晕。源代码以及编译信息如下:
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>
using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;
int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;
// ask for and read the midterm and final grades
cout << "Please enter your midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;
// ask for and read the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
vector<double> homework;
double x;
// invariant: homework contains all the homework grades read so far
while (cin >> x)
homework.push_back(x);
// check that the student entered some homework grades
typedef vector<double>::size_type vec_sz;
vec_sz size = homework.size();
if (size == 0) {
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}
// sort the grades
sort(homework.begin(), homework.end());
// compute the median homework grade
vec_sz mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid-1]) / 2
: homework[mid];
// compute and write the final grade
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * median
<< setprecision(prec) << endl;
return 0;
}
------------Configuration: CALCULATING STUDENT GRADES - Win32 Debug------------
Compiling...
CALCULATING STUDENT GRADES.CPP
d:\c++_projects\chapter 3\3-0-1 calculating student grades using middle grade\calculating student grades.cpp(37) : error C2653: 'vector<double,class std::allocator<double> >' : is not a class or namespace name
d:\c++_projects\chapter 3\3-0-1 calculating student grades using middle grade\calculating student grades.cpp(37) : error C2146: syntax error : missing ';' before identifier 'vec_sz'
d:\c++_projects\chapter 3\3-0-1 calculating student grades using middle grade\calculating student grades.cpp(37) : error C2065: 'vec_sz' : undeclared identifier
d:\c++_projects\chapter 3\3-0-1 calculating student grades using middle grade\calculating student grades.cpp(38) : error C2146: syntax error : missing ';' before identifier 'size'
d:\c++_projects\chapter 3\3-0-1 calculating student grades using middle grade\calculating student grades.cpp(38) : error C2065: 'size' : undeclared identifier
d:\c++_projects\chapter 3\3-0-1 calculating student grades using middle grade\calculating student grades.cpp(49) : error C2146: syntax error : missing ';' before identifier 'mid'
d:\c++_projects\chapter 3\3-0-1 calculating student grades using middle grade\calculating student grades.cpp(49) : error C2065: 'mid' : undeclared identifier
Error executing cl.exe.
CALCULATING STUDENT GRADES.exe - 7 error(s), 0 warning(s)
仔细阅读Debug的信息,发现是由于typedef vector<double>::size_type vec_sz;这条语句引起的,其他6条错误是这条错误的副作用。
在CSDN的论坛里翻了一遍,发现原来是编译器的问题。原来Visual C++ 6.0对C++标准支持得不是很好,所以书中的示例程序会有编译错误。"珍爱生命,远离VC6。"看来此话不是空穴来风。GOOGLE的时候很偶然地发现AC++的官网有关于VC++ 6.0不兼容问题的说明。因为是网页是英文的,所以我把它翻译了一下,希望对正在学习这本书而且又在用可恶的VC 6.0的朋友们有所帮助。
题目和本文题目一致,在此不再重复。全文如下:
请注意微软在Visual Studio.NET中已经修复了下文提到的所有问题,因此,仅当你使用早期的6.0版本时下列信息才对你有帮助。
Microsoft Visual C++ 6.0编译器(以下简称VC++6.0)对C++标准的某些方面支持得不好,这使得Accelerated C++此书中的某些例程无法顺利编译通过。本文列举了此类问题,并告诉你如何修改书中原来的代码以避免此类问题的出现。
这些问题中的一部分在整本书中都会重复出现。举个例子,任何影响学生成绩统计程序(即本文前文所提到的那个例子)的问题都将在第4、6、9和13章中出现,因为这个程序在所有其他的章节都会出现。我们首先描述那些普遍存在的问题(pervasive problems),然后在其后列出在每章中出现的特殊问题(specific problems)。
我们已经把这些问题(bug)报告给了微软,其工作人员正在致力于解决这些问题。举例来说,在描述普遍问题的第一部分,其中的问题#2在补丁包4(Service Pack 4)中已经被解决。
我们在每个例子中都提供了一个建议的替代方案(workaround)。这些替代方案通常使用#ifdef预处理命令,它和我们在§4.3/67中所讲解的#ifndef是类似的。如果在#ifdef它后面的预处理符号(the preprocessor symbol)被定义了,那么#ifdef命令会要求预处理器执行从#ifdef到对应的#endif之间的代码。微软的编译器定义了这个符号_MSC_VER作为预处理符号,我们用它来做测试,以使用条件包含(conditionally include)的功能。
普遍性问题(Pervasive bugs):
1、当你为一个容器类型(container type)写一个using声明时,比如vector类型:
using std::vector;
那么你就可以使用这个容器类型的成员了,尤其是类型成员,而不需要再费力做别的事情。比如:
vector<double>::size_type n = 0;
糟糕的是,VC++6.0却无法编译通过,它会抱怨类似下面这样的一句话
vector<double, class std::allocator<double> > : is not a class or namespace name
解决此问题最简单的权宜之计就是把std::包含进来,作为一个明确的修饰语,即使根本就没这个必要。
using std::vector;
#ifdef _MSC_VER
std::vector<double>::size_type n = 0;
#else
vector<double>::size_type n = 0;
#endif
2、补丁包4之前的VC++ 6.0不能很好地不会函数实参(function arguments)传递给模板函数(template functions)。这个问题会影响学生成绩统计这个在本书中会大量复现的程序。如果你像下面这样把compare作为一个实参传递给标准库sort函数的话,那么这个问题就会出现。
sort(vs.begin(), vs.end(), compare);
解决办法就是为Student_info定义<操作符,这样sort就可以自动使用它了。首先恰当地定义那个函数:
#ifdef _MSC_VER
bool operator<(const Student_info& x, const Student_info& y)
{
return x.name() < y.name();
}
#endif
然后像下面这样替换相关的调用(call):
#ifdef _MSC_VER
sort(vs.begin(), vs.end());
#else
sort(vs.begin(), vs.end(), compare);
#endif
还有一个更简单的办法,但是得用到直到第10章才会出现的语言特性。方法就是在compare的前面放一个明确的&操作符:
#ifdef _MSC_VER
sort(vs.begin(), vs.end(), &compare);
#else
sort(vs.begin(), vs.end(), compare);
#endif
我们相信补丁包4已经修复了这个问题。
3、微软的库无法恰当地加宽(pad)string类的输出。本来,像下面这样的调用是应该能够拉长输出使得在students[i].name中不管存什么值都会被延长到至少maxlen+1个字符。
cout << setw(maxlen+1) << students[i].name;
但是,VC++ 6.0的库根本就没有加宽的库函数,因此运行的时候名字会跑到下一行上去。
我们当初写这本书的时候,以为只要解决了这个问题就能使我们的程序正确。不幸的是,因为在测试代码工作上的不力,我们没有意识到,其实set是在左侧加宽而不是在右侧,然而这并不是我们想要的。
解决这两个问题最简单的办法就是直接明确的写出要求的加宽程序。比如:
#ifdef _MSC_VER
cout << students[i].name;
cout << string(maxlen - students[i].name.size() + 1, ' ');
#else
cout << setw(maxlen+1) << students[i].name;
#endif
我们已经在Accelerated C++此书的第二版及后续版本中采用了这个补救办法。
4、微软库没有定义本应在<algorithm>库中就能找到的min和max算法,解决办法就是自己定义一个叫做minmax.h的头文件,这样在任何一个用到这些函数的文件里只有把这个头文件包含进来就可以了。
#ifndef GUARD_minmax_H
#define GUARD_minmax_H
#ifdef _MSC_VER
// needed to cope with bug in MS library:
// it fails to define min/max
template <class T> inline T max(const T& a, const T& b)
{
return (a > b) ? a : b;
}
template <class T> inline T min(const T& a, const T& b)
{
return (a < b) ? a : b;
}
#endif
#endif
各章特殊说明(包括局部问题)(Notes about specific chapters (including localized bugs):):
第0-2章
这些章节中的代码在编译的时候应该没有问题。
第3章
这章的程序需要上面讲到的普遍性问题的解决办法。需要特殊说明的是,在47页上面vec_sz的定义需要这样改:
把
typedef vector<double>::size_type vec_sz;
改成
typedef std::vector<double>::size_type vec_sz;
第4-5章
这两章的程序需要上面讲到的普遍性问题的解决办法。
第6章
本章的程序需要上面讲到的普遍性问题的解决办法。
另外,尽管寻找URL的程序应该为isalpha和isalnum这两个字符分类函数(character classification function)使用using声明,但VC++ 6.0却错误地报告说这些函数不属于std命名空间。为了解决这个问题,你可以省略using声明。
#ifndef _MSC_VER
using std::isalpha;
using std::isalnum;
#endif
第7章
这章的程序需要解决上面提到的普遍性问题。
第8章
1、容器的泛型使用也会遇到上面提到的普遍性问题1,并且需要一个类似的解决办法。在泛型中值程序里,本来像下面这样写是合法的:
using std::vector;
typedef typename vector<T>::size_type vec_sz;
但是对于VC++ 6.0来说,我们必须改写成下面这样:
typedef typename std::vector<T>::size_type vec_sz;
2、在spit函数里,为std::isspace函数写一个using声明是没有问题的,但是像第6章里isalpha和isalnum函数一样,VC++ 6.0也会误报说isspace不在标准命名空间里。和第6章一样,别给isspace它写using声明。
第9章
这章的程序需要上面讲到的普遍性问题的解决办法。
第10章
这章的代码可以顺利通过编译。
第11章
std::allocator类的成员函数allocate被定义成只需要一个size_t类型的实参。VC++ 6.0却错误地需要第2个实参作为暗示(hint),以完善allocate的性能。如果一个空指针()传给第二个参数的话,那么就起不到任何暗示的作用。为了解决这个问题,我们需要重写任何一个调用allocate的函数来传递这个额外的,不标准的参数。
例如:我们可以重写Vec::create里allocate函数的调用:
#ifdef _MSC_VER
data = alloc.allocate(n, 0);
#else
data = alloc.allocate(n);
#endif
第12章
与第6、8章中VC++ 6.0无法保护标准命名空间中的字符分类函数(isalpha, isalnum, isspace等)一样,标准命名空间中的strlen函数同样无法被包含进来。解决办法是类似的:省略std::strlen这句话。
比如,需要调用一个const char*型参数的Str建立程序中:
#ifdef _MSC_VER
std::copy(cp, cp + strlen(cp), std::back_inserter(data));
#else
std::copy(cp, cp + std::strlen(cp), std::back_inserter(data));
#endif
第13章
这章的程序需要上面讲到的普遍性问题的解决办法。
另外,Grad类中clone函数的返回值类型需要像下面这样由书中的所写的Grad*改成Core*:
#ifdef _MSC_VER
Core* clone() const { return new Grad(*this); }
#else
Grad* clone() const { return new Grad(*this); }
#endif
之所以这样改是因为,给基类(base class)返回一个指针或引用的虚函数在VC++ 6.0里还不被允许在继承类返回一个指针或引用。
第14章
使用Handle或Ptr重新实现早期程序的代码必须按照相关章节中所描述的那样进行修改。举例来说,使用vector< Handle<Core> >学生成绩统计程序必须要对上面提到的普遍性问题进行修改。同样的道理,使用Ptr来重新实现的Str类也必须解决第12章里与strlen相关的问题。
第15章
像上面所讲到的那样,在各种使用标准库max算法的程序中,max的调用都必须改相应的你自己定义的版本的调用。
比如,在VCat_Pic里有这样一段代码:
class VCat_Pic: public Pic_base
{
//...
wd_sz width() const {
return std::max(top->width(), bottom->width());
}
必须重写成如下所示:
class VCat_Pic: public Pic_base
{
//...
wd_sz width() const {
#ifndef _MSC_VER
return std::max(top->width(), bottom->width());
#else
return max(top->width(), bottom->width());
}
当然,在这些代码所在的文件中你必须包含你自己编写的max版本的声明。
第16章
这章的程序需要解决上面提到的普遍性问题的解决方案。
以上就是我翻译的AC++官网关于VC++ 6.0不兼容问题的说明,有翻译不当的地方欢迎朋友们指出并讨论。