分享
 
 
 

Effective STL 条款30

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

条款30:确保目标区间足够大

STL容器在被添加时(通过insert、push_front、push_back等)自动扩展它们自己来容纳新对象。这工作的很好,有些程序员因为这个信仰而被麻痹,认为他们不必担心要为容器中的对象腾出空间,因为容器自己可以照顾好这些。如果是那样就好了!当程序员想向容器中插入对象但并没有告诉STL他们所想的时,问题出现了。这是一个常见的可以自我表现的方法:

int transmogrify(int x); // 这个函数从x

// 产生一些新值

vector<int> values;

... // 把数据放入values

vector<int> results; // 把transmogrify应用于

transform(values.begin(), values.end(), // values中的每个对象

results.end(), // 把这个返回的values

transmogrify); // 附加到results

// 这段代码有bug!

在本例中,transform被告知它的目的区间是从results.end()开始的,所以那就是开始写在values的每个元素上调用transmogrify的结果的地方。就像所有使用目标区间的算法,transform通过对目标区间的元素赋值的方法写入结果,transform会把transmogrify应用于values[0]并把结果赋给*results.end()。然后它会把transmogrify应用于value[1]并把结果赋给*(results.end()+1)。那只能带来灾难,因为在*results.end()没有对象,*(results.end()+1)也没有!调用transform是错误的,因为它会给不存在的对象赋值。(条款50解释了STL的一个调试实现怎么在运行期检测这个问题。)犯了这种错误的程序员几乎总是以为他们调用算法的结果能插入目标容器。如果那是你想要发生的,你就必须说出来。STL是一个库,不是一个精神。在本例中,说“请把transform的结果放入叫做results容器的结尾”的方式是调用back_inserter来产生指定目标区间起点的迭代器:

vector<int> results; // 把transmogrify应用于

transform(values.begin(), values.end(), // values中的每个对象,

back_inserter(results), // 在results的结尾

transmogrify); // 插入返回的values

在内部,back_inserter返回的迭代器会调用push_back,所以你可以在任何提供push_back的容器上使用back_inserter(也就是任何标准序列容器:vector、string、deque和list)。如果你想让一个算法在容器的前端插入东西,你可以使用front_inserter。在内部,front_inserter利用了push_front,所以front_insert只和提供那个成员函数的容器配合(也就是deque和list):

... // 同上

list<int> results; // results现在是list

transform(values.begin(), values.end(), // 在results前端

front_inserter(results), // 以反序

transmogrify); // 插入transform的结果

因为front_inserter用push_front把每个对象添加到results,results中的对象顺序会和values中对应的对象顺序相反。这也是为什么front_inserter没有back_inserter那么常用的原因之一。另一个原因是vector不提供push_front,所以front_inserter不能用于vector。

如果你要transform把输出结果放在results前端,但你也要输出和values中对应的对象顺序相同,只要以相反的顺序迭代values:

list<int> results; // 同上

transform(values.rbegin(), values.rend(), // 在results前端

front_inserter(results), // 插入transform的结果

transmogrify); // 保持相对的对象顺序

front_inserter让你强制算法在容器前端插入它们的结果,back_inserter让你告诉它们把结果放在容器后端,有点惊人的是inserter允许你强制算法把它们的结果插入容器中的任意位置:

vector<int> values; // 同上

...

vector<int> results; // 同上,除了现在

... // 在调用transform前

// results已经有一些数据

transform(values.begin(), values.end(), // 把transmogrify的

inserter(results, results.begin() + results.size()/2), // 结果插入

transmogrify); // results的中间

不管你是否使用了back_inserter、front_inserter或inserter,每次对目的区间的插入只完成一个对象。条款5解释了对于连续内存容器(vector、string和deque)来说这可能很昂贵,但条款5的建议解决方法(使用区间成员函数)不能应用于使用算法来完成插入的情况。在本例中,transform会对目的区间每次写入一个值,你无法改变。

当你要插入的容器是vector或string时,你可以通过按照条款14的建议最小化这个代价,预先调用reserve。你仍然要承受每次发生插入时移动元素的开销,但至少你避免了重新分配容器的内在内存:

vector<int> values; // 同上

vector<int> results;

...

results.reserve(results.size() + values.size()); // 确定results至少

// 还能装得下

// values.size()个元素

transform(values.begin(), values.end(), // 同上,

inserter(results, results.begin() + results.size() / 2),// 但results

transmogrify); // 没有任何重新分配操作

当使用reserve来提高一连串插入的效率时,总是应该记住reserve只增加容器的容量:容器的大小仍然没有改变。即使调用完reserve,当你想要让容器把新元素加入到vector或string时,你也必须对算法使用插入迭代器(比如,从back_inserter、front_inserter或inserter返回的迭代器之一)。

要把这些完全弄清楚,这里有一个提高本条款开始时的那个例子的效率的错误方法(就是我们把transmogrify作用于values里的数据的结果附加到results的那个例子):

vector<int> values; // 同上

vector<int> results;

...

results.reserve(results.size() + values.size()); // 同上

transform(values.begin(), values.end(), // 写入transmogrify的结果

results.end(), // 到未初始化的内存

transmogrify); // 行为未定义!

在这段代码中,transform愉快地试图对results尾部的原始的、未初始化的内存赋值。通常,这会造成运行期错误,因为赋值只在两个对象之间操作时有意义,而不是在一个对象和一块原始的比特之间。即使这段代码碰巧作了你想要它做的事情,results也不会知道transform在它的未使用容量上“创造”的新“对象”。直到results知道之前,它的大小在调用transform后仍然和原来一样。同样的,它的end迭代器仍和调用transform前指向同样的位置。结论呢?使用reserve而没有用插入迭代器会在算法内部导致未定义行为,也会弄乱容器。

正确地写这个例子的代码的方法是使用reserve和插入迭代器:

vector<int> values; // 同上

vector<int> results;

results.reserve(results.size() + values.size()); // 同上

transform(values.begin(), values.end(), // 把transmogrify的结果

back_inserter(results), // 写入results的结尾,

transmogrify); // 处理时避免了重新分配

到目前为止,我都在假设你让像transform那样的算法把它们的结果作为新元素插入容器。这是通常的期望,但有时候你要覆盖现有容器的元素而不是插入新的。当这种情况时,你不需要插入迭代器,但你仍然需要按照本条款的建议来确保你的目的区间足够大。

比如,假设你让transform覆盖results的元素。如果results至少有和values一样多的元素,那很简单。如果没有,你也必须使用resize来确保它有。

vector<int> values;

vector<int> results;

...

if (results.size() < values.size()){ // 确保results至少

results.resize(values.size()); // 和values一样大

}

transform(values.begin(), values.end(), // 覆盖values.size()个

results.begin(), // results的元素

transmogrify);

或者你可以清空results然后用通常的方式使用插入迭代器:

...

results.clear(); // 销毁results中

// 的所有元素

results.reserve(values.size()); // 保留足够空间

transform(values.begin(), values.end(), // 把transform地返回值

pack_inserter(results), // 放入results

transmogrify);

本条款论证了这个主题的很多变化,但我希望你能牢牢记住本质。无论何时你使用一个要求指定目的区间的算法,确保目的区间已经足够大或者在算法执行时可以增加大小。如果你选择增加大小,就使用插入迭代器,比如ostream_iterators或从back_inserter、front_inserter或inserter返回的迭代器。这是所有你需要记住的东西。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有