读书笔记之:Effective STL

条款3:使容器里对象的拷贝操作轻量而正确
STL中采用的都是拷贝对象的方式
如果所有这些使STL的拷贝机制听起来很疯狂,就请重新想想。是,STL进行了大量拷贝,但它通常设计为避免不必要的对象拷贝,实际上,它也被实现为避免不必要的对象拷贝。和C和C++内建容器的行为做个对比,下面的数组:
Widget w[maxNumWidgets];// 建立一个大小为maxNumWidgets的Widgets数组
// 默认构造每个元素
即使我们一般只使用其中的一些或者我们立刻使用从某个地方获取(比如,一个文件)的值覆盖每个默认构造的值,这也得构造maxNumWidgets个Widget对象。使用STL来代替数组,你可以使用一个可以在需要的时候增长的vector:
vector<Widget> vw;// 建立一个0个Widget对象的vector
// 需要的时候可以扩展
我们也可以建立一个可以足够包含maxNumWidgets个Widget的空vector,但没有构造Widget:
vector<Widget> vw;
vw.reserve(maxNumWidgets);// reserve的详细信息请参见条款14
和数组对比,STL容器更文明。它们只建立(通过拷贝)你需要的个数的对象,而且它们只在你指定的时候做。是的,我们需要知道STL容器使用了拷贝,但是别忘了一个事实:比起数组它们仍然是一个进步。

条款5:尽量使用区间成员函数代替它们的单元素兄弟

条款4:用empty来代替检查size()是否为0

条款6:警惕C++最令人恼怒的解析

C++里的一条通用规则——几乎任何东西都可能被分析成函数声明。
参数为函数指针的三种声明形式:
int g(double (*pf)());//最一般的方式
int g(double pf());// 同上;pf其实是一个指针
int g(double ());// 同上;参数名省略
所以对于下面的声明:
ist<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());
这声明了一个函数data,它的返回类型是list<int>。这个函数data带有两个参数:
●第一个参数叫做dataFile。它的类型是istream_iterator<int>。dataFile左右的括号是多余的而且被忽略。
●第二个参数没有名字。它的类型是指向一个没有参数而且返回istream_iterator<int>的函数的指针。

还有对于这条规则最常见的一个例子:
class Widget {...}; // 假设Widget有默认构造函数
Widget w();// 这是一个函数声明,并没有调用构造函数

条款8:永不建立auto_ptr的容器
条款18:避免使用vector<bool>
条款25:熟悉非标准散列容器
SGI设计的一个值得注意的方面是使用equal_to作为默认比较函数。这违背标准关联容器的约定——默认比较函数是less。这个设计结果不仅仅表示简单地改变默认比较函数。SGI的散列容器确定在一个散列容器中的两个对象是否有相同的值是通过相等测试,而不是等价(参见条款19)。对于散列容器来说,这不是一个不合理的决定,因为散列关联容器,不像它们在标准中的(通常基于树)兄弟,不需要保持有序。
Dinkumware设计的散列容器采取一些不同的策略。它仍然允许你指定对象类型、散列函数类型、比较函数类型和分配器类型,但是它把默认的散列和比较函数移进一个单独的类似特性的叫做hash_compare的类,而且它把hash_compare作为容器模板的HashingInfo实参的默认值。(如果你不熟悉“特性”类的概念,打开一本好的STL参考,比如Josuttis的《C++ Standard Library》[3]并学习char_traits和iterator_traits模板的动机和实现。)

在后端,SGI和Dinkumware的实现方法非常不同。SGI利用常用的一个元素的单链表的指针数组(桶)组成的开放散列法。Dinkumware也利用了开放散列法,但是它的设计是基于一种新颖的数据结构——由迭代器(本
质是桶)的数组组成的元素双向链表,迭代器的相邻对表示在每个桶里元素的范围。(细节可以参考Plauger相关主题的专栏,《Hash Tables》[16]。)
条款43:尽量用算法调用代替手写循环
有三个理由:
  • ● 效率:算法通常比程序员产生的循环更高效。
  • ● 正确性:写循环时比调用算法更容易产生错误。
  • ● 可维护性:算法通常使代码比相应的显式循环更干净、更直观。
公平的说,STL的实现者知道begin和end(以及类似的函数,比如size)用得很频繁,所以他们尽可能把它们实现得最高效。几乎肯定会inline它们,并努力编码使得绝大部分编译器都可以通过将计算结果提到循环外(译注:频度优化的一种)来避免重复计算。然而,经验表明,这样的实现不是总可以成功的,而且当不成功时,对重复计算的避免足以让算法比手写的循环具有性能优势。

条款44:尽量用成员函数代替同名的算法

有些容器拥有和STL算法同名的成员函数。关联容器提供了count、find、lower_bound、upper_bound和equal_range,而list提供了remove、remove_if、unique、sort、merge和reverse。大多数情况下,你应该用成员函数代替算法。这样做有两个理由。首先,成员函数更快。其次,比起算法来,它们与容器结合得更好(尤其是关联容器)。那是因为同名的算法和成员函数通常并不是是一样的。
效率不是find成员函数和find算法间的唯一差别。正如条款19所解释的,STL算法判断两个对象是否相同的方法是检查的是它们是否相等,而关联容器是用等价来测试它们的“同一性”。 因此,find算法搜索用的是相等,而find成员函数用的是等价。相等和等价间的区别可能造成成功搜索和不成功搜索的区别。比如说,条款19演示了用find算法在关联容器搜索失败而用find成员函数却搜索成功的情况!因此,如果使用关联容器的话,你应该尽量使用成员函数形式的find、count、lower_bound等等,而不是同名的算法,因为这些成员函数版本提供了和其它成员函数一致的行为。由于相等和等价间的差别,算法不能提供这样的一致行为。
对于标准的关联容器,选择成员函数而不是同名的算法有几个好处。首先,你得到的是对数时间而不是线性时间的性能。其次,你判断两个元素“相同”使用的是等价,这是关联容器的默认定义。第三,当操纵map和multimap时,你可以自动地只处理key值而不是(key, value)对。这三点给了优先使用成员函数完美的铁甲。
posted @ 2012-07-11 23:07  Mr.Rico  阅读(...)  评论(...编辑  收藏