条款36:了解copy_if的正确实现
STL有很多有趣的地方,其中一个是虽然有11个名字带“copy”的算法:
copy
copy_backward
replace_copy
reverse_copy
replace_copy_if
unique_copy
remove_copy
rotate_copy
remove_copy_if
partial_sort_copy
unintialized_copy
但没有一个是copy_if。这意味着你可以replace_copy_if,你可以remove_copy_if,你可以copy_backward或者reverse_copy,但如果你只是简单地想要拷贝一个区间中满足某个判断式(predicate)的元素,你只能自己做。
比如,假设你有一个函数来决定一个Widget是否有缺陷的:
bool isDefective(const Widget& w);
而且你希望把一个vector中所有有缺陷的Widget写到cerr。如果存在copy_if,你可以简单地这么做:
vector<Widget> widgets;
...
copy_if(widgets.begin(), widgets.end(), // 这无法编译:
ostream_iterator<Widget>(cerr, "\n"), // STL中并没有copyif
isDefective);
具有讽刺意味的事,copy_if是最初的惠普STL的一部分,惠普STL形成了STL的基础,而STL现在是标准C++库的一部分。有些怪癖会使历史变得有趣,在从HP STL中为标准筛选出一些大小易于管理的东西时,copy_if成了其中一个在剪接室中被遗弃的东西。
在《The C++ Programming Language》[7],Stroustrup强调写copy_if是微不足道的,他是对的,但那不意味着实现正确的琐事很简单。比如,这里有一个合理的看待copy_if,很多人(包括我)曾经知道的实现:
template<typename InputIterator, // 一个不很正确的
typename OutputIterator, // copy_if实现
typename Predicate>
OutputIterator copy_if(InputIterator begin,
InputIterator end,
OutputIterator destBegin, Predicate p)
{
return remove_copy_if(begin, end, destBegin, not1(p));
}
这个方法是基于这个观点——虽然STL并没有让你说“拷贝每个判断式为true的东西”,但它的确让你说了“拷贝除了判断式不为true以外的每个东西”。要实现copy_if,似乎我们需要做的就只是加一个not1在我们希望传给copy_if的判断式前面,然后把这个结果判断式传给remove_copy_if,结果就是上面的代码。
如果上面的理由有效,我们就可以用这种方法写出有缺陷的Widget:
copy_if(widgets.begin(), widgets.end(), // well-intentioned code
ostream_iterator<Widget>(cerr, "\n"), // that will not compile
isDefective);
你的STL平台将会敌视这段代码,因为它试图对isDefective应用not1(这个应用出现在copy_if内部)。正如条款41试图将清楚的,not1不能直接应用于一个函数指针,函数指针必须先传给ptr_fun。要调用这个copy_if实现,你必须传递的不仅是一个函数对象,而且是一个可适配的函数对象。这够简单了,但是想要成为STL算法用户的人应该不必这样。标准STL算法从来不要求它们的仿函数是可适配的,copy_if也不应该要求。上面的实现是好的,但不够好。
这是copy_if正确的微不足道的实现:
template<typename InputIterator, // 一个copy_if的
typename OutputIterator, // 正确实现
typename Predicate>
OutputIterator copy_if(InputIterator begin,
InputIterator end,
OutputIterator destBegin,
Predicate p) {
while (begin != end) {
if (p(*begin))*destBegin++ = *begin;
++begin;
}
return destBegin;
}
告诉你copy_if多有用,加上新STL程序员趋向于希望无论如何它应该存在的事实,所以好的做法是把copy_if——正确的那个!——放在你局部的STL相关工具库中,而且只要合适就使用。