免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: haoji
打印 上一主题 下一主题

《Effective STL》 [复制链接]

论坛徽章:
0
31 [报告]
发表于 2008-05-31 15:18 |只看该作者
一个非常重要的区间erase的表现是erase-remove惯用法。你可以在条款32了解到所有关于它的信息。
● 区间赋值。就像我在这个条款的一开始提到的,所有标准列容器都提供了区间形式的assign:
void container::assign(InputIterator begin, InputIterator end);
所以现在我们明白了,尽量使用区间成员函数来代替单元素兄弟的三个可靠的论点。区间成员函数更容易写,它们更清
楚地表达你的意图,而且它们提供了更高的性能。那是很难打败的三驾马车。

论坛徽章:
0
32 [报告]
发表于 2008-05-31 15:19 |只看该作者
Center of STL Study
——最优秀的STL学习网站
条款6:警惕C++最令人恼怒的解析
假设你有一个int的文件,你想要把那些int拷贝到一个list中。这看起来像是一个合理的方式:
ifstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), // 警告!这完成的并不
istream_iterator<int>()); // 是像你想象的那样
这里的想法是传一对istream_iterator给list的区间构造函数(参见条款5),因此把int从文件拷贝到list中。
这段代码可以编译,但在运行时,它什么都没做。它不会从文件中读出任何数据。它甚至不会建立一个list。
那是因为第二句并不声明list,而且它也不调用构造函数。其实它做的是,它做得很奇怪。我不敢直接告
诉你,因为你可能不相信我。取而代之的是,我得一点一点展开这个解释。你坐下了吗?如果没有,你可能
要找找附近有没有椅子
我们会从最基本的开始。这行声明了一个函数f带有一个double而且返回一个int:
int f(double d);
第二行作了同样的事情。名为d的参数左右的括号是多余的,被忽略:
int f(double (d)); // 同上;d左右的括号被忽略
下面这行声明了同样的函数。它只是省略了参数名:
int f(double); // 同上;参数名被省略
你应该很熟悉这三种声明形式了吧,虽然可以把括号放在参数名左右这一点可能比较新。(在不久以前我也
觉得它新。)
现在让我们再看看三个函数声明。第一个声明了一个函数g,它带有一个参数,那个参数是指向一个没有参

论坛徽章:
0
33 [报告]
发表于 2008-05-31 15:19 |只看该作者
数、返回double的函数的指针:
int g(double (*pf)()); // g带有一个指向函数的指针作为参数
这是完成同一件事的另一种方式。唯一的不同是pf使用非指针语法来声明(一个在C和C++中都有效的语
法):
int g(double pf()); // 同上;pf其实是一个指针
照常,参数名可以省略,所以这是g的第三种声明,去掉了pf这个名字:
int g(double ()); // 同上;参数名省略
注意参数名左右的括号(就像f的第二种声明中的d)和单独的括号(正如本例)之间的区别。参数名左右的
括号被忽略,但单独的括号指出存在一个参数列表:它们声明了存在指向函数的指针的参数。
用这些f和g的声明做了热身,我们准备检查本条款开头的代码。这里再写一遍:
list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());
打起精神,这声明了一个函数data,它的返回类型是list<int>。这个函数data带有两个参数:
● 第一个参数叫做dataFile。它的类型是istream_iterator<int>。dataFile左右的括号是多余的而且被忽略。
● 第二个参数没有名字。它的类型是指向一个没有参数而且返回istream_iterator<int>的函数的指针。
奇怪吗?但这符合C++里的一条通用规则——几乎任何东西都可能被分析成函数声明。如果你用C++编程有
一段时间了,你应该会遇到另一个这条规则的表象。有多少次你会看见这个错误?
class Widget {...}; // 假设Widget有默认构造函数
Widget w(); // 嗯哦
这并没有声明一个叫做w的Widget,它声明了一个叫作w的没有参数且返回Widget的函数。学会识别这个失言
(faux pas)是成为C++程序员的一个真正的通过仪式。
所有这些都很有趣(以它自己的扭曲方式),但它没有帮我们说出我们想要说的,也就是应该用一个文件的

论坛徽章:
0
34 [报告]
发表于 2008-05-31 15:20 |只看该作者
内容来初始化一个list<int>对象。现在我们知道了我们必须战胜的解析,那就很容易表示了。用括号包围一个
实参的声明是不合法的,但用括号包围一个函数调用的观点是合法的,所以通过增加一对括号,我们强迫编
译器以我们的方式看事情:
list<int> data((istream_iterator<int>(dataFile)), // 注意在list构造函数
istream_iterator<int>()); // 的第一个实参左右的
// 新括号
这是可能的声明数据方法,给予istream_iterators的实用性和区间构造函数(再次,参见条款5),值得知道它
是怎样完成的。
不幸的是,目前并非所有编译器都知道它。在我测试的几种中,几乎一半拒绝接受数据的声明,除非它错误
地接受没有附加括号形式的声明!为了安慰这样的编译器,你可以滚你眼睛并且使用我辛辛苦苦解释的错误
的数据声明,但那将不可移植而且目光短浅。毕竟,目前编译器存在的分析错误肯定会在将来被修正,是
吧?(肯定的!)
一个更好的解决办法是在数据声明中从时髦地使用匿名istream_iterator对象后退一步,仅仅给那些迭代器名
字。以下代码到哪里都能工作:
ifstream dataFile("ints.dat");
istream_iterator<int> dataBegin(dataFile);
istream_iterator<int> dataEnd;
list<int> data(dataBegin, dataEnd);
命名迭代器对象的使用和普通的STL编程风格相反,但是你得判断这种方法对编译器和必须使用编译器的人
都模棱两可的代码是一个值得付出的代价。

论坛徽章:
0
35 [报告]
发表于 2008-05-31 15:21 |只看该作者
Center of STL Study
——最优秀的STL学习网站
条款7:当使用new得指针的容器时,记得在销毁容器前delete那些指针
STL中的容器非常优秀。它们提供了前向和逆向遍历的迭代器(通过begin、end、rbegin等);它们能告诉你
所容纳的对象类型(通过value_type的typedef);在插入和删除中,它们负责任何需要的内存管理;它们报告
容纳了多少对象和最多可能容纳的数量(分别通过size和max_size);而且当然当容器自己被销毁时会自动销
毁容纳的每个对象。
给了这样聪明的容器,很多程序员不再担心用完以后的清除工作。呵呵,他们说,他们的容器会帮他们解决
那个麻烦。在很多情况下,他们是对的,但当容器容纳的是指向通过new分配的对象的指针时,他们就错
了。的确,当一个指针的容器被销毁时,会销毁它(那个容器)包含的每个元素,但指针的“析构函数”是
无操作!它肯定不会调用delete。
结果,下面代码直接导致一个内存泄漏:
void doSomething()
{
vector<Widget*> vwp;
for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)
vwp.push_back(new Widget);
... // 使用vwp
} // Widgets在这里泄漏!
当vwp除了生存域后,vwp的每个元素都被销毁,但那并不改变从没有把delete作用于new得到的对象这个事
实。那样的删除是你的职责,而不是vector的。这是一个特性。只有你知道一个指针是否应该被删除。
通常,你需要它们被删除。当情况如此时,可以很简单地实现:
void doSomething()
{
vector<Widget*> vwp;
... // 同上
for (vector<Widget*>::iterator i = vwp.begin();
i != vwp.end(),

论坛徽章:
0
36 [报告]
发表于 2008-05-31 15:21 |只看该作者
++i) {
delete *i;
}
}
这可以工作,除非你不是对你“工作”的意思很吹毛求疵。一个问题是新的for循环代码比for_each多得多,
但没有使用for_each来的清楚(参见条款43)。另一个问题是这段代码不是异常安全的。如果在用指针填充了
vwp和你要删除它们之间抛出了一个异常,你会再次资源泄漏。幸运的是,两个问题都可以克服。
要把你的类似for_each的循环转化为真正使用for_each,你需要把delete转入一个函数对象中。这像儿戏般简
单,假设你有一个喜欢和STL一起玩的孩子:
template<typename T>
struct DeleteObject : // 条款40描述了为什么
public unary_function<const T*, void> { // 这里有这个继承
void operator()(const T* ptr) const
{
delete ptr;
}
};
现在你可以这么做:
void doSomething()
{
... // 同上
for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>);
}
不幸的是,这让你指定了DeleteObject将会删除的对象的类型(在本例中是Widget)。那是很讨厌的,vwp是
一个vector<Widget*>,所以当然DeleteObject会删除Widget*指针!咄!这种冗余就不光是讨厌了,因为它会导
致很难跟踪到的bug。假设,比如,有的人恶意地故意从string继承:
class SpecialString: public string { ... };
这是很危险的行为,因为string,就像所有的标准STL容器,缺少虚析构函数,而从没有虚析构函数的类公有

论坛徽章:
0
37 [报告]
发表于 2008-05-31 15:22 |只看该作者
继承是一个大的C++禁忌。(详细信息参考任意好的C++书。在《Effective C++》中,要看的地方是条款
14。)但是,仍有一些人做这种事,所以让我们考虑一下下面代码会有什么行为:
void doSomething()
{
deque<SpecialString*> dssp;
...
for_each(dssp.begin(), dssp.end(), // 行为未定义!通过没有
DeleteObject<string>()); // 虚析构函数的基类
} // 指针来删除派生对象
注意dssp被声明为容纳SpecialString*指针,但for_each循环的作者告诉DeleteObject它将删除string*指针。很容易
知道会出现什么样的错误。SpecialString的行为当然很像string,所以如果它的用户偶尔忘了他们用的是
SpecialStrings而不是string是可以原谅的。
你可以通过编译器推断传给DeleteObject:perator()的指针的类型来消除这个错误(也减少DeleteObject的用户
需要的击键次数)。我们需要做的所有的事就是把模板化从DeleteObject移到它的operator():
struct DeleteObject { // 删除这里的
// 模板化和基类
template<typename T> // 模板化加在这里
void operator()(const T* ptr) const
{
delete ptr;
}
}
编译器知道传给DeleteObject:perator()的指针的类型,所以我们可以让它通过指针的类型自动实例化一个
operator()。这种类型演绎下降让我们放弃使DeleteObject可适配的能力(参见条款40)。想想DeleteObject的设
计目的,会很难想象那会是一个问题。
使用新版DeleteObject,用于SpecialString的客户代码看起来像这样:
void doSomething()
{
deque<SpecialString*> dssp;
...

论坛徽章:
0
38 [报告]
发表于 2008-05-31 15:22 |只看该作者
for_each(dssp.begin(), dssp.end(),
DeleteObject()); // 啊!良好定义的行为!
}
直截了当而且类型安全,正如我们喜欢的一样。
但仍不是异常安全的。如果在SpecialString被new但在调用for_each之前抛出一个异常,就会发生泄漏。那个问
题可以以多种方式被解决,但最简单的可能是用智能指针的容器来代替指针的容器,典型的是引用计数指
针。(如果你不熟悉智能指针的概念,你应该可以在任何中级或高级C++书上找到描述。在《More Effective
C++》中,这段材料在条款28。)
STL本身没有包含引用计数指针,而且写一个好的——一个总是可以正确工作的——非常需要技巧,除非必
须否则你一定不想做。我在1996年的《More Effective C++》中发布了用于引用计数智能指针的代码,尽管是
基于已制定的智能指针实现并提交给由有经验的开发人员做了很多发布前检查,小bug报告还是持续了很多
年。智能指针出错的不同方式的数量很值得注意。(详细信息请参考《More Effective C++》错误列表
[28]。)
幸运的是,基本上不需要你自己写,因为经过检验的实现不难找到。一个这样的智能指针是Boost库(参见条
款50)中的shared_ptr。利用Boost的shared_ptr,本条款的原始例子可以重写为这样:
void doSomething()
{
typedef boost::shared_ ptr<Widget> SPW; //SPW = "shared_ptr
// to Widget"
vector<SPW> vwp;
for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)
vwp.push_back(SPW(new Widget)); // 从一个Widget建立SPW,
// 然后进行一次push_back
... // 使用vwp
} // 这里没有Widget泄漏,甚至
// 在上面代码中抛出异常
你不能有的愚蠢思想是你可以通过建立auto_ptr的容器来形成可以自动删除的指针。那是很可怕的想法,非
常危险。我在条款8讨论了为什么你应该避免它。
我们需要记住的所有事情就是STL容器很智能,但它们没有智能到知道是否应该删除它们所包含的指针。当
你要删除指针的容器时要避免资源泄漏,你必须用智能引用计数指针对象(比如Boost的shared_ptr)来代替

论坛徽章:
0
39 [报告]
发表于 2008-05-31 15:23 |只看该作者
指针,或者你必须在容器销毁前手动删除容器中的每个指针。
最后,你可能会想到既然一个类似DeleteObject的结构体可以简化避免容纳对象指针的容器资源泄漏,那么是
否可能建立一个类似的DeleteArray结构体来简化避免容纳指向数组的指针的容器资源泄漏呢?这当然可能,
但是否明智则不是同一回事了。条款13解释了为什么动态分配数组几乎总是劣于vector和string对象,所以在
你要写DeleteArray之前,请先看看条款13。运气好的话,你会知道DeleteArray是永远不会出现的结构体。

论坛徽章:
0
40 [报告]
发表于 2008-05-31 15:23 |只看该作者
Center of STL Study
——最优秀的STL学习网站
条款8:永不建立auto_ptr的容器
坦白地说,本条款不需要出现在《Effective STL》里。auto_ptr的容器(COAPs)是禁止的。试图使用它们的
代码都不能编译。C++ 标准委员会花费了无数努力来安排这种情况[1]。我本来不需要说有关COAPs的任何东
西,因为你的编译器对这样的容器应该有很多抱怨,而且所有那些都是不能编译的。
唉,很多程序员使用STL平台不会拒绝COAPs。更糟的是,很多程序员妄想地把COAPs看作简单、直接、高
效地解决经常伴随指针容器(参见条款7和33)资源泄漏的方案。结果,很多程序员被引诱去使用COAPs,即
使建立它们不应该成功。
我会马上解释COAPs的幽灵有多令人担心,以至于标准化委员会采取特殊措施来保证它们不合法。现在,我
要专注于一个不需要auto_ptr甚至容器知识的缺点:COAPs不可移植。它们能是怎么样的?C++标准禁止他
们,比较好的STL平台已经实现了。可以有足够理由推断随着时间的推移,目前不能实现标准的这个方面的
STL平台将变得更适应,并且当那发生时,使用COAPs的代码将更比现在更不可移植。如果你重视移植性
(并且你应该是),你将仅仅因为它们的移植测试失败而拒绝COAPs。
但可能你没有移植性思想。如果是这样,请允许我提醒你拷贝auto_ptr的独特——有的人说是奇异——的定
义。
当你拷贝一个auto_ptr时,auto_ptr所指向对象的所有权被转移到拷贝的auto_ptr,而被拷贝的auto_ptr被设为
NULL。你正确地说一遍:拷贝一个auto_ptr将改变它的值:
auto_ptr<Widget> pw1(new Widget); // pw1指向一个Widget
auto_ptr<Widget> pw2(pw1); // pw2指向pw1的Widget;
// pw1被设为NULL。(Widget的
// 所有权从pw1转移到pw2。)
pw1 = pw2; // pw1现在再次指向Widget;
// pw2被设为NULL
这非常不寻常,也许它很有趣,但你(作为STL的用户)关心的原因是它导致一些非常令人惊讶的行为。例
如,考虑这段看起来很正确的代码,它建立一个auto_ptr<Widget>的vector,然后使用一个比较指向的Widget
的值的函数对它进行排序。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP