附录B:在微软STL平台上的注意事项

优质
小牛编辑
126浏览
2023-12-01

在这本书的开头几页里,我提到了术语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的库实现里的已知漏洞并解释怎样修改你的库副本来减少它的缺陷。不用说,编辑你的库头文件是让你自己冒险的事。如果你遇到麻烦,不要责备我。