Effective STL条款21

原创 2003年06月26日 10:11:00

条款21: 永远让比较函数对相等的值返回false

让我向你展示一些比较酷的东西。建立一个set,比较类型用less_equal,然后插入一个10:

set<int, less_equal<int> > s;    // s以“<=”排序
s.insert(10);      // 插入10

现在尝试再插入一次10:

s.insert(10);

对于这个insert的调用,set必须先要判断出10是否已经位于其中了。 我们知道它是,但set可是木头木脑的,它必须执行检查。为了便于弄明白发生了什么,我们将一开始已经在set中的10称为10A,而正试图插入的那个10叫10B。

set遍历它的内部数据结构以查找哪儿适合插入10B。最终,它总要检查10B是否与10A相同。关联容器对“相同”的定义是等价(equivalence)(参见条款19),因此set测试10B是否等价于10A。当执行这个测试时,它自然是使用set的比较函数。在这一例子里,是operator<=,因为我们指定set的比较函数为less_equal,而less_equal意思就是operator<=。于是,set将计算这个表达式是否为真:

!(10A <= 10B) && !(10B <= 10A)    // 测试10A和10B是否等价

哦,10A和10B都是10,因此,10A <= 10B 肯定为真。同样清楚的是,10B <= 10A。于是上述的表达式简化为

!(true) && !(true)

再简化就是

false && false

结果当然是false。 也就是说,set得出的结论是10A与10B不等价,因此不一样,于是它将10B插入容器中10A的旁边。在技术上而言,这个做法导致未定义的行为,但是通常的结果是set以拥有了两个为10的值的拷贝而告终,也就是说它不再是一个set了。通过使用less_equal作为我们的比较类型,我们破坏了容器!此外,任何对相等的值返回true的比较函数都会做同样的事情。根据定义,相等的值却不是等价的!是不是很酷?

OK,也许你对酷的定义和我不一样。就算这样,你仍然需要确保你用在关联容器上的比较函数总是对相等的值返回false。但是,你需要保持警惕。对这条规则的违反容易达到令人吃惊的后果。

举个例子,条款20描述了该如何写一个比较函数以使得容纳string*指针的容器根据string的值排序,而不是对指针的值排序。那个比较函数是按升序排序的,但我们现在假设你需要string*指针的容器的降序排序的比较函数。自然是抓现成的代码来修改了。如果你不细心,可能会这么干,我已经加亮了对条款20中代码作了改变的部分:

struct StringPtrGreater:    // 高亮显示
public binary_function<const string*,  // 这段代码和89页的改变
const string*,     // 当心,这代码是有瑕疵的!
bool> {
 bool operator()(const string *ps1, const string *ps2) const
 {
  return !(*ps1 < *ps2);   // 只是相反了旧的测试;
 }      // 这是不对的!
};

这里的想法是通过将比较函数内部结果取反来达到反序的结果。很不幸,取反“<”不会给你(你所期望的)“>”,它给你的是“>=”。而你现在知道,因为它将对相等的值返回true,对关联容器来说,它是一个无效的比较函数。

你真正需要的比较类型是这个:

struct StringPtrGreater:     // 对关联容器来说
public binary_function<const string*,   // 这是有效的比较类型
const string*,
bool> {
 bool operator()(const string *ps1, const string *ps2) const
 {
  return *ps2 < *ps1;   // 返回*ps2是否
 }      // 大于*ps1(也就是
};       // 交换操作数的顺序)

要避免掉入这个陷阱,你所要记住的就是比较函数的返回值表明的是在此函数定义的排序方式下,一个值是否大于另一个。相等的值绝不该一个大于另一个,所以比较函数总应该对相等的值返回false。

唉。

我知道你在想什么。你正在想,“当然,这对set和map很有意义,因为这些容器不能容纳复本。但是multiset和multimap怎么样呢?那些容器可以容纳复本,那些容器可能包含副本,因此,如果容器认为两个值相等的对象不等价,我需要注意些什么?它将会把两个都存储进去的,这正是multi系列容器的所要支持的事情。没有问题,对吧?”

错。想知道为什么,让我们返回头去看最初的例子,但这次使用的是一个mulitset:

multiset<int, less_equal<int> > s;   // s仍然以“<=”排序
s.insert(10);     // 插入10A
s.insert(10);     // 插入10B

现在,s里有两个10的拷贝,因此我们期望如果我们在它上面做一个equal_range,我们将会得到一对指出包含这两个拷贝的范围的迭代器。但那是不可能的。equal_range,虽然叫这个名字,但不是指示出相等的值的范围,而是等价的值的范围。在这个例子中,s的比较函数说10A和10B是不等价的,所以不可能让它们同时出现在equal_range所指示的范围内。

你明白了吗?除非你的比较函数总是为相等的值返回false,你将会打破所有的标准关联型容器,不管它们是否允许存储复本。

从技术上说,用于排序关联容器的比较函数必须在它们所比较的对象上定义一个“严格的弱序化(strict weak ordering)”。(传给sort等算法(参见条款31)的比较函数也有同样的限制)。如果你对严格的弱序化含义的细节感兴趣,可在很多全面的STL参考书中找到,比如Josuttis的《The C++ Standard Library》[3](译注:中译本《C++标准程序库》P176),Austern的《Generic Programming and the STL》(译注:中译本《泛型程序设计与STL》)[4],和SGI STL的网站[21]。 我从未发现这个细节如此重要,但一个对严格的弱序化的要求直接指向了这个条款。那个要求就是任何一个定义了严格的弱序化的函数都必须在传入相同的值的两个拷贝时返回false。

嗨! 这就是这个条款!

effective C++ 读书笔记 条款21

effective C++ 读书笔记 条款21
  • djb100316878
  • djb100316878
  • 2014年11月06日 16:22
  • 892

effective stl 第19条:理解相等(equality)和等价(equivalence)的区别

#include #include #includeusing namespace std;bool ciStringCompare(const string l, const string r) {...
  • u014110320
  • u014110320
  • 2016年09月20日 23:36
  • 251

Effective STL条款17-条款18

条款17:使用交换技巧来修正过剩容量本节条款告诉我们,如果你有一个vector的容器,容器的容量是10000,但是,现在只用了1,那么为了节省内存,我们应该只保留使用的vector容量,多余的容量应该...
  • u011058765
  • u011058765
  • 2016年04月21日 09:10
  • 290

Effective STL学习笔记-条款19

条款19 了解相等和等价的区别了解相等和等价的区别例如find函数,或者一个set容器插入一个值得时候都会进行比较。但是它们的行为是不同的,find是通过 operator==,而set::inser...
  • gx864102252
  • gx864102252
  • 2017年08月27日 21:01
  • 105

Effective C++ 条款21

必须返回对象时,别妄想返回其reference我们上节分析了对象引用传递的好处,现在说明函数返回引用对象带来的种种坏处。 先来一段代码:class Rational{ public: Rat...
  • u011058765
  • u011058765
  • 2015年06月26日 10:03
  • 456

Effective STL 中文版(完整版)

 Winter总算找到《Effective STL》的完整中文版了,奉献给大家。书中作者解释了怎样结合STL组件来在库的设计得到最大的好处。这样的信息允许你对简单、直接的问题开发简单、直接的解决方案,...
  • WinterTree
  • WinterTree
  • 2005年01月16日 01:23
  • 16310

Effective STL条款21

条款21: 永远让比较函数对相等的值返回false让我向你展示一些比较酷的东西。建立一个set,比较类型用less_equal,然后插入一个10:set > s; // s以“...
  • 521
  • 521
  • 2004年12月31日 01:32
  • 1088

《Effective C++》:条款28-条款29

条款28避免返回handles指向对象内部成分:指的是不能返回对象内部数据/函数的引用、指针等。 条款29为异常安全而努力是值得的:指的是要有异常处理机制,避免发生异常时造成资源泄露等问题。...
  • KangRoger
  • KangRoger
  • 2015年02月19日 19:47
  • 1394

Effective C++ 条款2

尽量以const、enum、inline替换#define首先,大家要明白一个道理。#define是什么,有什么作用。很简单,大家都知道#define实现宏定义,如下代码:#define Flag 1...
  • u011058765
  • u011058765
  • 2015年06月19日 12:06
  • 498

《Effective C++》:条款41-条款42

条款41了解隐式接口和编译期多态 条款42了解typename的双重意义条款
  • KangRoger
  • KangRoger
  • 2015年03月10日 22:13
  • 1242
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Effective STL条款21
举报原因:
原因补充:

(最多只允许输入30个字)