附录B:在微软STL平台上的注意事项
在这本书的开头几页里,我提到了术语STL平台是指一个特定编译器和一个标准模板库特定实现的组合。如果你在使用版本6或更早的Microsoft Visual C++编译器(即,伴随版本6或更早的Microsoft Visual Studio的编译器),在编译器和库之间的区别特别重要,因为编译器有时比伴随的STL实现更有能力。在本附录中,我描述了旧的微软STL平台的一个重要缺点,而且我提供可以明显改进你STL经验的变通办法。
下面是给使用Microsoft Visual C++(MSVC)4-6版的开发者的信息。如果你正使用Visual C++ .NET,你的STL平台没有下面描述的那些问题,你可以略过这个附录。
STL里的成员函数模板
假设你有两个Widget的vector,你想把一个vector里的Widget拷贝到另一个的末端。那很容易。只要使用vector的区间insert函数(参见条款5):
vector<Widget> vw1, vw2; ... vw1.insert(vw1.end(), vw2.begin(), vw2.end()); // 把vw2里Widget的副本 // 追加到vw1
如果你有一个vector和一个deque,你一样可以这么做:
vector<Widget> vw; deque<Widget> dw; vw.insert(vw.end(), dw.begin(), dw.end()); // 把dw里Widget的副本 // 追加到vw
实际上,你不必理会被拷贝的容器容纳的是什么类型的对象。即使自定义容器也可以工作:
vector<Widget> vw; ... list <Widget> lw; ... vw.insert(vw.begin(), lw.begin(), lw.end()); // 把lw里Widget的副本 // 追加到vw set<Widget> sw; ... vw.insert(vw.begin(), sw.begin(), sw.end()); // 把sw里Widget的副本 // 追加到vw template<typename T, // 用于自定义 typename Allocator = allocator<T> > // 兼容STL的 class SpecialContainer { ... }; // 容器模板 SpecialContainer<Widget> scw; ... vw.insert(vw.end(), scw.begin(), scw.end()); // 把scw里Widget的副本 // 追加到vw
这种灵活性是可能的,因为vector的区间insert函数完全不是一个函数。相反,它是一个成员函数模板,可以用任何迭代器类型实例化,以产生一个具体的区间insert函数。对于vector,标准像这样声明insert模板:
template <class T, class Allocator = allocator<T> > class vector { public: ... template <class InputIterator> void insert(iterator position, InputIterator first, InputIterator last); ... };
每个标准容器都要求提供这个模板化的区间insert。容器也要求提供类似成员函数模板的区间构造函数和assign的区间形式(两者都在条款5讨论)。
MSVC版本4-6
不幸的是,伴随MSVC版本4-6的STL实现没有声明成员函数模板。这个库最初为MSVC版本4开发,而那个编译器,像它所在时代的大多数编译器一样,缺乏成员函数模板的能力。在MSVC4到MSVC6之间,编译器增加了对这些模板的支持,但是,由于法律诉讼的影响,微软没有直接包含它们,库基本保持冻结。
因为伴随MSVC4-6的STL实现是为一个缺乏成员函数模板的编译器设计的,库的作者通过用具体函数替换每个模板的方法来近似这样的功能性,也就是只接受和容器的迭代器类型相同的迭代器。例如,对于insert,这个成员函数模板被替换这样:
void insert(iterator position, // “iterator”是 iterator first, iterator last); // 容器的迭代器类型
受限的区间成员函数形式可以进行从一个vector<Widget>到一个vector<Widget>或从一个list<int>到一个list<int>的区间插入,但不能从一个vector<Widget>到一个list<Widget>或从一个set<int>到一个deque<int>。甚至不可能进行从一个vector<long>到一个vector<int>的区间insert(或assign或构造),因为vector<long>::iterator与vector<int>::iterator类型不同。结果,下面十分有效的代码不能用MSVC4-6编译:
istream iterator<Widget> begin(cin), end; // 建立用于从cin // 读取Widget的 // begin和end迭代器 // (参见条款6) vector<Widget> vw(begin, end); // 把cin的Widget读入vw // (再次参见条款6);在MSVC4-6 // 不能编译 list<Widget> lw; ... lw.assign(vw.rbegin(), vw.rend()); // 把vw的内容赋值给lw // (以反序);在MSVC4-6 // 不能编译 SpecialContainer<Widget> scw; ... scw.insert(scw.end(), lw.begin(), lw.end()); // 把lw中的Widget的副本 // 插入scw的末端; // 在MSVC4-6不能编译
那如果你必须使用MSVC4-6,你该怎么办?那取决于你使用的MSVC版本和是否你被迫使用伴随编译器的STL实现。
MSVC4-5的变通办法
再次看看不能用伴随MSVC4-6的STL编译的有效代码例子:
vector<Widget> vw(begin, end); // 被MSVC4-6的 // STL实现拒绝 list<Widget> lw; ... lw.assign(vw.rbegin(), vw.rend()); // 也拒绝 SpecialContainer<Widget> scw; ... scw.insert(scw.end(), lw.begin(), lw.end()); // 同上
这些调用看起来相当不同,但它们全都由于相同的原因而失败:在STL实现里缺乏成员函数模板。对它们有一种单独的治疗方法:使用copy和插入迭代器(参见条款30)。例如,这里是上面例子的变通办法:
istream_iterator<Widget> begin(cin), end; vector<Widget> vw; // 默认构造vw; copy(begin, end, back_inserter(vw)); // 然后把cin中的 // Widget拷贝进去 list<Widget> lw; ... lw.clear(); // 去除lw的老 copy(vw.rbegin(), vw.rend(), back_inserter(lw)); // Widget;把 // vw的Widget拷贝进去(以 // 反序) SpecialContainer<Widget> scw; ... copy(lw.begin(), lw.end(), // 把lw的Widget拷贝到 inserter(scw, scw.end())); // scw的结尾
我鼓励你在伴随MSVC4-5的库上使用这样的基于copy的变通办法,但是注意!不要满足于这个变通办法,你忘记了它们只是变通办法。正如条款5解释的,使用copy算法几乎总是不如使用一个区间成员函数,所以一旦你有机会把你的STL平台升级到支持成员函数模板的版本,就在区间成员函数是正确方法的地方停止使用copy。
用于MSVC6的另一个变通办法
你也可以对MSVC6使用MSVC4-5的变通办法,但对于MSVC6有另一个选择。作为MSVC4-5一部分的编译器没有提供有意义的成员函数模板,所以STL实现缺乏它们的事实是无关紧要的。MSVC6的形势则不同,因为MSVC6的编译器支持成员函数模板。因此有理由考虑用提供标准指定的成员函数模板的STL实现替换伴随MSVC6的。
条款50解释了SGI和STLport都提供了可以自由下载的STL实现,而且那两个实现都把MSVC6编译器作为将配合的编译器之一。你也可以从Dinkumware购买最新的兼容MSVC的STL实现。每种选择各有利弊。
SGI的和STLport的实现是自由的,我想你知道那在对软件的官方支持上代表什么:完全没有。而且,因为SGI和STLport把他们的库设计为使用多种编译器,你或许必须手工配置它们的实现来最有效地使用MSVC6。特别是,你可能必须明确地启用成员函数模板的支持,因为,它们要使用很多编译器,SGI和/或STLport默认可能不启用它。你可能也得为与其他MSVC6库(特别是DLL)链接而担心,包括保证你使用合适的线程和调试构建,等等。
如果你被那些事吓着了,或如果你听过你负担不起自由软件的牢骚,你可能要看看Dinkumware用于MSVC6的替代库。它被设计为提高原生MSVC6 STL的兼容性,并使作为STL平台的MSVC6对标准的支持最大化。因为Dinkumware写了伴随MSVC6的STL,所以他们最新的STL实现有很大的可能性真的是一个合适的替代品。要了解更多关于Dinkumware STL实现的信息,访问公司的网站:http://www.dinkumware.com/。
不管你选择的是SGI的、STLport的还是Dinkumware的实现作为STL的替代品,你将得到的不只是带有成员函数模板的STL。你也将在库的其他地方旁路一致性问题,比如没有声明push_back的string。此外,你可以访问有用的STL扩展,包括散列容器(参见条款25)和单链表(slists)。SGI的和STLport的实现也提供了多种非标准的仿函数类,比如select1st和select2nd(参见条款50)。
即使你被伴随MSVC6的STL实现困住,访问Dinkumware网站或许也是值得的。那个网站列举了在MSVC6的库实现里的已知漏洞并解释怎样修改你的库副本来减少它的缺陷。不用说,编辑你的库头文件是让你自己冒险的事。如果你遇到麻烦,不要责备我。