本部分对应<<Effective STL>>中item43-item50,作者在详细讲解了STL各部分(容器、算法、迭代器、函数对象等)的编程所需的注意事项后在整个STL的角度上综合所需的注意事项,包括算法和成员函数的效率对比,查找算法的对比,编译诊断以及互联网上相关资源。
Item43:Prefer algorithm calls to hand-written loops.
手写循环可以实现的如对容器中每个对象执行特定的操作可以用for_each、transform等实现, 查找特定的对象可以用find、count、equal_range、bianry_search等来实现。使用算法在下面三个方面有较大的优势:
1、Efficiency
影响效率的主要因素是算法设计者更了解容器的具体实现,可以使用库使用者无法采用的方式进行优化遍历。如deque采用分块线性结构进行存储,算法设计者可以使用指针进行遍历而使用者只能使用迭代器进行遍历(较慢)。
2、Correctness
手写循环容易出错,算法可以简单的完成手写循环较难实现的操作。如考虑读取数组每个元素元素加上41并插入deque前,手写循环需要考虑插入顺序以及insert对容器迭代器的失效影响,最终可能的代码为
deque<double>::iterator insertLocation = d.begin();
for (size_t i = 0; i < numDoubles; ++i) {
insertLocation =
d.insert(insertLocation, data[i] + 41);
++insertLocation;
}
而用算法可以一条语句就行
transform(data, data + numDoubles,
inserter(d, d.begin()),
bind2nd(plus<double>(), 41));
3、Maintainability
算法更能体现循环操作到底做了什么。
但不是绝对的,如在vector中找第一个比x大比y小的数,用算法实现则非常麻烦。算法可能需要函数对象(functor)或者函数适配器(绑定器(binder)、求反器(negator)),一般来说使用容器的代码应较少使用手写循环。
Item44:Prefer member functions to algorithms with the same names.
为什么?成员函数和容器有更好的结合性,效率更高。考虑算法find应用于set为线性时间,而成员find为对数时间。对map的影响更改,算法需要比较key和value而成员函数只比较key值。
对于关联容器,尽量使用成员函数形式的find、count、lower_bound等等,list提供成员函数remove(真正删去元素)、remove_if、unique、sort、merge和reverse等。甚至sort算法不能应用于list(要求random-access-iterator)。
Item45:Distinguish among count, find, binary search, lower_bound, upper_bound, and equal_range.
这些都是查找算法,根据查找要求不同可以对其进行区分。
1、对于有序区间:binary_search、lower_bound、upper_bound和equal_range(对数时间)
2、无序区间:count、count_if、find和find_if,(线性时间)。
3、无序区间查找对象是否存在find(第一个符合条件)效率略高于count(整个区间).
4、有序区间查找对象是否存在用binary_search, 若需定位则要用equal_range。若用lower_bound,则我们需要对其进行如下测试:
vector<Widget>::iterator i = lower_bound(vw.begin(), vw.end(), w);
if (i != vw.end() && *i == w) {
...
}
&&后半部分这涉及到equality和equivalence测试的不一致,可能会出错。而使用equal_range,我们只需要进行如下测试即可:
IterPair p = equal_range(vw.begin(), vw.end(), w);
if (p.first != p.second) {
...
}
5、当使用标准关联容器时,则可以用对应的成员函数(count、find、equal_range、lower_bound或upper_bound),binary_search没有对应的成员函数。
6、总结。
问题 | 无序序列 | 有序序列 | set/map | multiset/multimap |
是否存在? | find | binary_search | count | find |
是否存在?在哪? | find | equal_range | find | find/lower_bound |
第一个不在期望值之前的对象在哪? | find_if | lower_bound | lower_bound | lower_bound |
第一个在期望值之后的对象在哪? | find_if | upper_bound | upper_bound | upper_bound |
有多少个? | count | equal_range then distance | count | count |
等于期望值的所有对象在哪? | find(迭代) | equal_range | equal_range | equal_range |
Item46:Consider function objects instead of functions as algorithm parameters.
一般而言抽象层次越高,效率越低,那么为什么要优先使用函数对象呢?inline!
考虑降序排序vector<double>, 使用function objects如下:
vector<double> v;
...
sort(v.begin(), v.end(), greater<double>());
编译器会在实例化模板时内联函数,从而使sort没有函数调用的过程。直接使用function如下:
inline bool doubleGreater(double d1, double d2)
{
return dl > d2;
}
...
sort(v.begin(), v.end(), doubleGreater);
函数参数真正传递的是函数指针,编译器实例化sort时产生的函数声明如下:
void sort(vector<double>::iterator first,
vector<double>::iterator last,
bool (*comp)(double, double)
);
则
每次需要比较函数时编译器都会通过指针产生一个间接函数调用。
Item47:Avoid producing write-only code.
这里所说的write-only code是指那些容易写出来却难以维护的代码,考虑在vector<int>中去掉所有位于大于y的最后一个元素之后并且小于等于x的所有元素,可能的实现如下:
vector<int> v;
int x, y;
...
v.erase(
remove_if(
find_if(v.rbegin(), v.rend(),
bind2nd(greater_equal<int>(), y)).base(),
v.end(),
bind2nd(less<int>(), x)
),
v.end()
);
当我们把所有的元素都替换为fn则为v.f1(f2(f3(v.f4(), v.f5(), f6(f7(), y)),.f8(), v.f9(), f6(f10(), x)), v.f9()),这么多的嵌套是难以接受的。
软件维护的时间比开发时间多的多,不能维护的软件几乎没有什么价值,所以在享受STL带来便捷的更应该从维护者的角度进行设置。对于这个问题我们可以分解为两步:
1、用reverse_iterator查找大于y的最后一个元素的位置。
2、使用erase_remove惯用法删除。
typedef vector<int>::iterator VecIntIter;
// find
VecIntIter rangeBegin = find_if(v.rbegin(), v.rend(),
bind2nd(greater_equal<int>(), y)).base();
// erase-remove
v.erase(remove_if(rangeBegin, v.end(), bind2nd(less<int>(), x)), v.end());
Item48:Always #include the proper headers.
无论何时引用std中的元素, 都要加入相应的头文件。由于某些平台一些对象包含了一些头文件,如vector可能包含<string>,编译器可能允许你在使用vector和string时缺少#include<string>,但这样子做不是portable的。
Item 49. Learn to decipher STL-related compiler diagnostics.
STL编译错误产生的错误信息十分繁杂,通常达上千字符。通常的做法是替换复杂的模板来简化出错信息,如可以用string来替换std::basic_string<char, std::char_traits<char>, std::allocator<char> >,具体的做法可以参见《 An STL Error Message Decryptor for Visual C++》和相关信息。若编译器无法知道你用的组件,有可能是缺少了相应的头文件。
Item 50. Familiarize yourself with STL-related web sites.
三个跟STL相关的网站,SGI STL、STL port、Boost。
1、 SGI STL除了提供STL组件全面的文档外,还提供了一些非标准组件。散列关联容器(hash_set,hash_map等), 单链表容器(slist), 超长字符串容器(ropes)和一些非标准函数对象和适配器(select1st, compose1等)。SGI STL还提供了多线程容器安全的相关资料,c++ IO库实现等资料。
2、STL port 增强了STL的可移植性。
3、Boost是c++的扩充类库,如可以被容器接受的智能指针share_ptr。由于Boost非常庞大,可以按需学习。