<![endif]-->
说明:将修润、印务和一些较为明显的笔误、错乱、讨论予以删除,仅仅保留有探讨和学习意义的部分。依照上述要求完成此份摘录,以下为摘录部分:
本勘误档更新日期: 2005/02/01 《STL 源码剖析》繁体版
碁峰信息股份有限公司 出版, 2002
注意:以下各项修正皆以日期排序
书籍内容更正,有两种作法,一是在因特网上做个专属勘误网页,让大家上去看。这是比较实时的作法。而更理想更负责的作法是:不但有勘误网页,并且在新刷中予以更正 -- 如果有新刷的话。不过,理想与现实之间需要一点协调。书籍的制作是这样的,制版与印刷时,是以台(8 或 16 页)为单位。因此,每换一页,同台的各页统统要换过。这便造成印制成本的大量增加。以前,我从不考虑成本,只要我认为书籍内容有修改必要,即使只是某个字词用得不甚理想,我都会请出版社更新。出版社也都全力配合(这一点让我非常感谢)。慢慢地,我的行事不再这么霹雳,我觉得我多少也要站在出版社的立场想想。所以我打算,如果是关系到对错正误的根本性问题,我便一定在新刷修正。如果是易判别的错别字或排版误失或用词不很恰当… 等等,我便先在勘误网页上明载,但不求立刻于新刷中更正。直到收集来的这类误失较为密集了,才一并于下一刷修正。哪些是新刷已修正的,哪些是暂请读者自行动手更改的,我会在勘误网页(网址见书封底)上很清楚地说明。这是个便宜法门,请读者见谅。书籍应该在出版前就详细检查,以完美之姿出现。但是完美很难达到。对于下列大大小小轻重不等的误失,我谨向读者说抱歉。本档欢迎广为流传,谢谢。以下为更新记录。如果您购买的是第 n 刷,请将以下第 n+1 刷之后的更新内容自行修正至书上。谢谢
以下暂请读者自行更正:
(注意,Lm 表示第 m 行,L-n 表示倒数第 n 行)
■p44,L-10
原文:等同于 new( const void*) p) T(x)
更正:等同于 new( ( void*) p) T(x)
讨论:p 转型为 (const void*) 之后,将成为一个 non-const pointer to const value ,那么就无法在该指针所指位置执行 construction 动作了。那可不妙。所以我认为不该有 const 。如果转型为 (void* const) 倒是可以(实验证实)。
提示 :请同时修改《泛型程序设计与 STL 》p189, L11.
感谢:jaulueng
日期:2002/06/01
■p122,L-1 (补充)
原文: // 将原vector 的备用空间中的内容也忠实拷贝过来(侯捷疑惑:啥用途?)
讨论:此批注所在之函式 insert_aux(iterator position, const T& x); 允许我们在任意位置 position 安插元素值 x 。如果此函式被 push_back() 呼叫(如本页),由于 position 将会是 end() ,所以上述批注所言那行的确没有用途。但 insert_aux() 也可能被 insert(iterator p, const T& x); 呼叫,此时
insert_aux() 的参数 position 可能不是 end() ,那么上述批注所言那行动作
就有必要了(此时它并非拷贝备用空间的内容,而是拷贝安插点之后的内容)。
修改: // 将安插点后的原内容也拷贝过来(提示:本函式也可能被 insert(p,x) 呼叫)
感谢:ikai
日期:2002/06/01
■p176,L7 (不够好。重新修润)
原文:
执行一个所谓的 percolate down (下放)程序: 将根节点(最大值被取走后,形成一个「洞」)填入上述那个失去生存空间的叶节点值,再将它拿来和其两个子节点比较键值(key ), 并与较大子节点对调位置。
修改:
执行一个所谓的 percolate down (下放)程序:
将上述那个失去生存空间的叶节点值填入根节点(最大值被取走后,根节点形成一个「洞」),再将该值拿来和当前的两个子节点比较, 并与较大子节点对调位置。
感谢:jjhou
日期:2002/06/01
★ 注意:这样的修润仍是不正确的,后面续有讨论。为保持讨论之完整性,本条仍保留。
■p178,L9 (补充)
原文:// 依侯捷之见,下面直接改为 *(first + holeIndex) = value; 应该可以。
修改:请在其下加一行如下:
// 读者响应:不可如此,试套4.7.4 节范例即知。侯捷测试:验证后的确不行。
读者来信:
如果以此法套用 p.181 例子,先做一个 heap 9 7 8 3 5 0 2 3 1 4 ,然后 pop_heap ,结果 heap 将变成 8 7 2 3 5 0 4 3 1 而正确结果应该是 8 7 4 3 5 0 2 3 1 。故不可直接改为 *(first + holeIndex) = value;
感谢:ikai
日期:2002/06/01
★ 注意:这样的修润仍是不正确的,后面续有讨论。为保持讨论之完整性,本条仍保留。
■p23,L-9~L-7 (补充)
原文:
#ifdef __STL_NEED_TYPENAME
# define typename // 侯捷:难道不该 #define typename class 吗?
#endif
讨论:
的确不该如我所言。在整个 SGI STL 中,template<> 内用的关键词是 "class" 。
关键词 "typename" 只用于除此之外的其余地方(告诉编译程序该处将出现一个
name of type (如果编译程序推论能力够强,可以不依赖这个关键词);在那种地方当然不能将关键词 "typename" 以关键词 "class" 替换之;应替换为 none. 这个道理就像本页接下来的三行,当编译程序不支援时, 就将 "explicit" 替换none.
感谢:Wesley Bao
日期:2002/07/24
■p67 chunk_alloc() 作法讨论
读者来函:
当 memory pool 还有空间但不够一个区块大小时,处理如下:
if ( bytes_left > 0 ) {
obj * volatile * my_free_list =
free_list + FREELIST_INDEX ( byte_left );
((obj *) start_free)->free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
}
两个问题:
1. 当 byte_left < 4 时﹐而 sizeof(obj) 为 4; 以下这句话: ((obj *) start_free)->free_list_link = *my_free_list; 岂不是要出问题?
2. 即使 byte_left > 4 ,但小于区块尺寸大小﹐将来又有什么用呢?例如当 byte_left 为 5 ,则 my_free_list 指向第一个区块,而此区块尺寸为 8 ,当用户要配置 7 时岂不是会出问题?
侯捷回复:
1. 注意本函式最前批注(p66 ):size 已适当上调至 8 的倍数
2. 你的问题出在对 p67,L13 「以下试着让记忆池中的残余零头还有利用价值」的疑惑. 我举个例子,如果此刻要求一个大小为 24 的 obj ,而此刻 free_list[2] 无法供应任何内存,于是向 memory pool 求助,假设此时 pool 剩余(必为 8 倍数)16 ,于是将这 16 挂到 free_list[1] 最前端(这就是残余零头的利用),然后再向系统索求 24*(40+n)... (之后的动作详见 p68 第二段)注意,memory pool 是指图2-7 右下角那块红色区域,不是指 free_list[ ] 所维护的 linked lists (虽然有时候可能会混着说)。
感谢:Sam_Yang
日期:2002/07/24
■p67 SGI STL allocator 作法讨论
读者来函:SGI STL allocator 所关连的整个 free_list[ ] (如图2-7 )在何时何处被释放?
侯捷回复:不释放,一直持有(有点 garbage collection 的味道),等待再被运用。直到程序结束,自然就被系统收回。这里没有 memory leak 的问题,因为一切都在掌控之下,所有内存都可再被利用。
感谢:抱歉,佚失
日期:2002/07/24
■p156,L9 (勘误)
原文:最后缓冲区 已无(或只剩一个) 元素备用空间
修改:最后缓冲区 只剩一个 元素备用空间
读者说明:L17 说:也就是说只有当最后一个缓冲区只剩一个备用元素空间...
侯捷回应:同意。我重新检测整个算法,确定 deque 的任何缓冲区的确不可能无备用空间。当元素备用空间为 1 而即将被使用时,p156 的 push_back_aux() 会先被唤起,配置新缓冲区(成为备用空间),然后才将原剩的那个备用元素空间拿来用。
感谢:jaulueng
日期:2002/07/24
■p176 1,2 段;p178 L8,L9 (勘误)
修改1 :以下是 p176 一、二两段修改后之完整文字:
图4-22 是pop_heap 算法的实际操演情况。既然身为max-heap ,最大值必然在根节点。pop 动作将根节点取走 — 其实是设至底部容器vector 之尾端节点 — 后(逻辑上此刻可将根节点设想成一个「空洞节点」),为满足 "complete" binary tree 的条件,必须割舍最下层最右端叶节点,并将其值重新安插至max-heap (因此有必要重新调整heap 结构)。为满足max-heap 次序特性(每个节点的键值都大于或等于其子节点键值),我们执行所谓的 percolate down (下放)程序:将空洞节点和其较大子节点「对调」,并持续下放直至叶节点为止。然后将前述被割舍之元素值设给这个「已到达叶层之空洞节点」,再对它执行一次 percolate up (上溯)程序。这便大功告成。
修改2 :以下两行请这么修改:
p178 L8 // 将欲调整值填入目前的洞号内。注意,此时肯定满足次序特性。
修改为: // 此时(可能)尚未满足次序特性。执行一次 percolate up 动作。
p178 L9 // 依侯捷之见,下面...
请删除
感谢:jaulueng
日期:2002/07/24
■p182,L5,L6 (补充)
原文:
// array 无法动态改变大小,因此不可以对满载的 array 做 push_heap() 动作。
// 因为那得先在 array 尾端增加一个元素。
修改:
// array 无法动态改变大小,因此不可以对满载的 array 做 push-heap 动作。
// 因为那得先在 array 尾端增加一个元素。 如果对一个满载的 array 执行
// push_heap() ,该函式会将最后一个元素视为新增元素,并将其余元素视为
// 一个完整的 heap 结构(实际上它们的确是),因此执行后的结果等于原先的 heap 。
感谢:jaulueng
日期:2002/07/24
■p210,L1 (补充)
原文:我们先对 P,G 做一次单旋转, 再 更改 P,G 颜色,即可...
修改:我们先对 P,G 做一次单旋转, 并 更改 P,G 颜色,即可...
说明:从 p226 L7~L9 可看出是先改变颜色再旋转,但两者次序其实无关紧要,因此略做如上修改。p210,p211 是逻辑观念的说明,和 p226 之程序代码不见得一一对应(p226 之程序代码有许多实作上的巧妙安排)。
感谢:jaulueng
日期:2002/07/24
■p211, 图 5-15c (讨论)
讨论:以状况3 来说,RB-tree 是不会旋转的,其结果会是:key80 、90 两节点颜色由红变黑,key85 节点颜色由黑变红,树形维持不变。同理,p211, 图 5-15d 状况4 亦然,只需改变颜色,不须旋转树形。
回复:状况3 不改树形,只改颜色,前提是 L2 所言「此时如果 GG 为黑」。至于状况4 ,不理解如何能够「不改树形,只改颜色」?
感谢:jaulueng
日期:2002/07/24
■p212, 5.2.2 一个由上而下的程序(讨论)
读者来函:
由 p225~p226 __rb__tree_rebalance() 源码知道它并不是「一个由上而下的程序」,而是一个 bottom-up procedure 。以图5-15e 为例,当新增 key35 节点时,
根据 p225 L-8~L-2 源码可知,直接判断父节点和伯父节点是否皆为红,而非如 p212,L4 所说「沿着 A 的路径... 」
建议:将 top-down procedure 等相关文字改成 bottom-up procedure ,将图5-15e 的虚线箭头方向改成指向 key30 节点。
回复:本段算法描述系以下列书籍 18.5 节为依据(见本书 p469 介绍):
"Algorithms, Data Structures and Problem Solving with C++"
by Mark Allen Weiss, AW, 1995
这份描述与 SGI STL 实作手法略有差异,但不能据此认为这份描述不对。SGI STL 之实作手法的确如您所言。此手法完全依据以下书籍 p266,p268 提供之 pseudo code :
"Introduction to Algorithms", by Thomas Ho. Cormen, etc. 该书在 RB-tree 主题上的文字描述甚少。
感谢:jaulueng
日期:2002/07/24
■p212,L-2 (讨论及修正)
原文:要不直接安插,要不安插后再一次 单 旋转即可。
读者来函:看不出为何须「再一次单旋转」
回复:如果此时新插节点之父节点为红,就应该再执行旋转。本页所举例子不需再旋转。
修改:要不直接安插,要不安插后 (若父节点为红) 再一次旋转 (单双皆可能) 即可
感谢:jaulueng
日期:2002/07/24
■p226,L-10; p227,L14 (勘误)
原文:// ... 此时 必须做 树形旋转(及颜色改变,在程序它处)。
修改:// ... 此时 可能需 树形旋转(及颜色改变,在程序它处)。
提示:p232 图5-19 M => N 的过程即可看出,当新节点(红)之父节点为红,在 M 的情况下只需做颜色改变,不需树形旋转
感谢:jaulueng
日期:2002/07/24
■p81,auto_ptr (讨论)
读者来函:
我发现 auto_ptr 有一个问题,就是它的 T *pointee 变量为私有的,在实际使用中不能编译通过。如下例﹕
auto_ptr<int> aInt(new int[10]);
auto_ptr<char> aChar = aInt;
编译时﹐出现如下错误:
error C2248: 'pointee' : cannot access private member
declared in class 'auto_ptr<int>'
我是在 VC6 下编译的,或许在 gcc 下可以编译通过,我还没有试过。于是我在 VC6 下将 pointee 改为 public ,然后编译通过。当然这样做有背信息隐蔽原则,或许在 VC6 下也可以保持 private ,也能编译通过,如果可以,可否告诉我,谢谢。
侯捷回复:
(1) pointee 不宜宣告为 private.
(2) 完整的 auto_ptr 可参考 C++ 标准链接库,或《More Effective C++ 》p291. (有 bug )或《C++ 标准链接库》p56.
(3) 请认识 member template 的用途。
(4) 不存在针对 array 而写的 auto_ptr ,见《C++ 标准链接库》p47 。
(5) 建议详读《C++ 标准链接库》4.2 节,其中有 auto_ptr 的发展动机、错误运用、运用实例、设计细目
感谢:larrin
日期:2002/07/24
■p125 (讨论)
读者来函:
vector::insert(iterator position, size_type n, const T& x) 函式中呼叫了:
fill(position, position + n, x_copy)
uninitialized_fill_n(finish, n - elems_after, x_copy)
fill(position, old_finish, x_copy)
为什么其中引入并使用 x 的副本 x_copy ﹐使用 x 岂不更好?STL 中有多处类似作法,例如 p156 deque::push_back_aux() 。
侯捷回复:
的确应该可以直接使用 x 。 从逻辑思考角度 ,x 以 by reference 方式传入,如果又以 by reference 方式传递给 fill() 和 uninitialized_fill_n() ,
由于去处函式都进行拷贝动作,并不影响 x 。所以直接以 x 传递无妨。 从实证角度 ,我如上更改了 SGI STL 源码,可以正确运作。
感谢:popiano
日期:2002/07/27
■p264,L1 (讨论)
原文: // 从己方的 buckets vector 尾端开始,插入 n 个元素,其值为 null 指标。
// 注意,此时 buckets vector 为空,所以所谓尾端,就是起头处。
读者来函: 此时 hastable 是空的,但 buckets vector 并不为空,而是存放
buckets.size() 个 null 指针。以上注释所说明的,是其下一行动作:buckes.insert(buckets.end(), ht.buckets.size(), (node*) 0); 其意义是把 buckets vector 尾部因先前 reserve() 而扩充出来的部份初始化为 null 指标。当然,reserve() 也可能根本没有实际扩充 vector ,那么上述一行程序不会执行任何实质性操作。
侯捷回复: 我想您可能错看了 p263 L-5 的意义 :buckets.clear() 您可能以为它执行的是 hashtable<>::clear() (就在 p263 )事实上它执行的是 vector<>::clear() ,而其动作就是将整个 vector 清空,使大小为 0 ( ref. 《C++ 标准链接库》p154 )。因此我的注释是正确的。顺带一提,以下值得修改:
p263,L-6
原文:这动作是呼叫 vector::clear , 造成所有元素为 0
修改:这动作是呼叫 vector::clear , 将整个容器清空
感谢:yyf
日期:2002/07/31
■p398,L4 (勘误)
原文: // RW STL 采用一般教科书写法(直观地对左半段和右半段递归),较易阅读。
修改: (请将此行拿掉)
说明: 从 p400 上部的 RW STL 源码可看出,其算法策略和 SGI STL 类似,只是
它更进一步把左右两段中较长的一段以递归方式处理,较短一段由 while 循环继续处理,并没有对左右两段分别递归处理。
检讨:同意。但 yyf 将长短搞反了 :)
顺带一提,以下值得修改:
p400,L5
原文:__quick_sort_loop(cut, last); // 对右段递归处理
修改:__quick_sort_loop(cut, last); // 较短段以递归方式处理
p400,L10
原文:__quick_sort_loop(first, cut); // 对左段递归处理
修改:__quick_sort_loop(first, cut); // 较短段以递归方式处理
感谢:yyf
日期:2002/07/31
■ 第2 章关于 memory pool (讨论)
yongwu.wang 2002/09/25 来信:
关于 __default_alloc_template ﹐我写了一些代码测试﹐发现对于大于 128bytes 的
分配没有问题﹐但是当我分配一些小于 128bytes 的 blocks 时
确实用上了 memory pool ﹐并用 free list 串起来﹐整个分配过程都很正常。
但我认为释放时存在问题﹐释放时调用的函数
只是简单的调整指针﹐并没有真正释放 memory ﹐这也可以理解﹐因为只是释放一个块﹐
不应该释放整个 pool 。那么整个 pool 在什么地方释放呢﹖
我发现 __default_alloc_template class 并没有 destructor ﹐
memory pool 没有被释放﹐我跟踪了memory 分配和释放函数﹐确实发生了memory leak 。
我的分析对吗﹖这是 SGI 的 bug 吗﹖
侯捷回复:这不是 bug ,这是设计理念。
memory pool 一般并不释放 blocks 。因为它认为资源由它管理。这不算 memory leak 。
但这的确有缺点。应该适时释放(一些)blocks 。这是 SGI allocator 值得加强的部分
至于程序结束前一刻没有释放整个 pool ,那没关系,modern OS 自会收回这一部份,
不影响其他 process 。
欢迎参考 programmer-13-memory-pool.htm
感谢:yongwu.wang
日期:2002/09/25
■ 讨论
读者来函:
hi, mr. hou
there is a question on STL, look at this statement:
m_OneMap.insert(pair<Key, T>(key_value, t_value)); // (1)
Error reported while compiling at Solaris 5.8 with RW STL,
then everything is OK after I changed it into:
m_OneMap.insert(value_type<Key, T>(key_value, t_value)); // (2)
when compiling with VC with P.J. STL, no error ocurred here.
why?
thanks!
侯捷回复:
式(1) 是正确的。为什么它在 Solaris 5.8 with RW STL 无法正常编译,我不知道。
我在 BCB5 with RW STL 中正确编译了它。
式(2) 的意思是,以 map<...>::value_type<Key,T> 产生一个暂时对象
并以 key_value,t_value 为初值。根据以下 SGI STL 源码:
template <class Key, class T, class Compare = less<Key>, class Alloc=alloc>
class map {
public:
typedef Key key_type;
typedef T data_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type; // 元素型别(键值/ 实值)
typedef Compare key_compare; // 键值比较函式
...
};
也就是产生了一个 pair<const Key, T>(key_value,t_value) 。拿它来和 (1) 相比,
只差一个 const 。既然 (1) 不被接受,可推想 Solaris 5.8 with RW STL 在 constness 上面的限制很硬。请注意,使用 STL 时,很多时候(包括上述情况)都只因为卡在 constness ,程序便编译不了。constness 实在不可轻忽。
感谢:Collin Lee
日期:2003/02/20
■ 讨论
读者来函:
p305 中间,关于 iota 的意义。
The name iota is taken from the programming language APL.
而APL 语言主要是做数学计算的,在数学中有很多公式会借用希腊字母,
希腊字母表中有这样一个字母,大写为Ι ,小写为ι ,它的英文拼写正好是iota ,这个字母在θ(theta) 和κ(kappa) 之间!您可以看看http://www.wikipedia.org/wiki/APL_programming_language
下面有一段是这样的:
APL is renowned for using a set of non-ASCII symbols that are an extension of traditional arithmetic and algebraic notation. These cryptic symbols, some have joked, make it possible to construct an entire air traffic control system in two lines of code. Because of its condensed nature and non-standard characters, APL has sometimes been termed a "write-only language", and reading an APL program can feel like decoding an alien tongue. Because of the unusual character-set, many programmers used special APL keyboards in the production of APL code. Nowadays there are various ways to write APL code using only ASCII characters.
不知您怎么看?
侯捷回复:谢谢您充实我的知识 :)
感谢:aihua woo
日期:2003/02/20
■ 讨论
读者来函:
我最近在网络上找一些关于STL 的技术文章,正好搜索到您的主页上的STL 系列文章 (PDF) 。关于第二篇文章中最后(17 页末)您所写的逐行读取的iterator 代码,只要稍加修改即可在VC7 下运行(VC 6 尚未测试过)。修改就是增加一个 operator != 的友元函数:
class myistream_line_iterator {
friend bool
operator!= (const myistream_line_iterator& x,
const myistream_line_iterator& y);
// 以下略…
};
inline bool operator!=(const myistream_line_iterator& x,
const myistream_line_iterator& y)
{
return !operator==(x,y);
}
由于工作环境主要是VC ,所以渴望技术上的成果能在工作中运用。
另:由于接触 MFC 已有一些时间了,考虑将其转移到G++ 编译程序上。您认为是否可行?我见MFC 中的代码都比较标准规范,转移的难度似乎比ATL 要小很多。在 G++ 下写WINDOWS 平台的COM 组件实在是件比较麻烦的事情,不知道有没有合适的类库可以借助?多谢!
侯捷回复:
您对程序的修改,我在 VC6 中试了,仍然不能过关。我已经把 MFC 的骨干移植到 BCB and GCC 去了(请见 MFCLite )。我移植的是不涉及 UI 的部分;也不涉及 COM 。
感谢:sclzmbie
日期:2003/02/20
■ 讨论
读者来函:
我十分困扰的一个问题是,不知道如何使用 C++ 的 wchar_t 和 wstring ,尤其不会用 locale 。我用的是VC++ 6.0 ,关于多字节字符和宽字符的帮助很少。C++ 著作中对此问题也很少涉及,或者没有恰当例子,不容易看懂。下面是我写的一个小程序,只能显示前面七个单字节字符,无法显示后面四个宽字符:
// cb5[o] vc6[o] g295[x]
#include <string>
#include <iostream>
using namespace std;
void main()
{
wstring s=L"Windows 操作系统";
int n=s.length(); // n==11
for(int i=0; i<n; i++)
cout << s[i] << " ";
// 87 105 110 100 111 119 115 25805 20316 31995 32113
wcout << endl;
}
侯捷回复:待我研究研究 :)
感谢:chenxiaohe2
日期:2003/02/20
■ 附录(补充)
读者来函:书后附录关于VC6.0 移植。使用 VC IDE 进行link 时,需要将stlport 的libpath 引入。但这时会有个小问题:由于同时指定了MT 选项,link 时会去寻找libcmt.lib ,总是报告libcmtd.lib 与libcmt.lib 冲突。只有将libcmt.lib 禁用,才能link 通过。
感谢:microhard
日期:2005/02/01
-- the end