Effective STL - 为指针的关联容器指定比较函数

本文介绍如何为指针的关联容器指定正确的比较函数,确保容器内的元素能够根据指针指向对象的值进行正确排序,而非指针本身的地址值。

 

条款20:为指针的关联容器指定比较函数

假定你有一个string*指针的set,你把一些动物的名字插入进set:

set<string*> ssp;						// ssp = “set of string ptrs”
ssp.insert(new string("Anteater"));
ssp.insert(new string("Wombat"));
ssp.insert(new string("Lemur"));
ssp.insert(new string("Penguin"));

然后你写了下列代码打印set的内容,希望字符串按字母顺序出现。就像你期望set保持它们的内容有序一样。

for (set<string*>::const_iterator i = ssp.begin();		// 你希望看到
			i != ssp.end();			// 这个:“Anteater”
			++i)				// “Lemur”,“Penguin”,
	cout << *i << endl;				// “Wombat”

注释描述了你希望看见的,但结果却不是这样的。取而代之的是,你看见四个十六进制的数,它们是指针的值。因为set容纳的是指针,*i不一个string,它是一个string*。如果你使用的是copy算法,

copy(ssp.begin(), ssp.end(), // 把ssp中的字符串

ostream_iterator<string>(cout, "/n")); // 拷贝到cout (但这 不能编译)

你会发现这个copy的调用将不能通过编译,ostream_iterator需要知道被打印的对象的类型,所以当你告诉它是一个string时(通过作为模板参数传递),编译器检测到那和ssp中储存的对象类型(是string*)之间不匹配,它们会提示编译错误。

 

如果你愤怒地把显式循环中的*i改为**i,你可能可以得到你想要的输出,但也可能不。是的,动物名字将被打印,但它们按字母顺序出现的机会只是24份之1。ssp保持它的内容有序,但是它容纳的是指针,所以它以指针的值排序,而不以string值。对于四个指针值可能有24

排列(译注:4! = 4 * 3 * 2 * 1 = 24),所以指针被储存时有24种可能的顺序。因此你看见字符串按字母排序有24份之1的几率。[

 

为了克服这个问题,你应该回忆起

set<string*> ssp;

是这个的简写:

set<string*, less<string*> > ssp;

好,为了完全准确,它是

set<string*, less<string*>, allocator<string*> > 的简化,但是分配器在本条款里与我们无关,所以我们将忽略它们。


如果你想要string*指针以字符串值确定顺序被储存在set中,你不能使用默认比较仿函数类less<string*>。你必须改为写你自己的比较仿函数类,它的对象带有string*指针并按照指向的字符串值来进行排序。就像这样:

struct StringPtrLess:
	public binary_function<const string*,
				const string*,		
				bool> {
	bool operator()(const string *ps1, const string *ps2) const
	{
		return *ps1 < *ps2;
	}
};

然后你可以使用StringPtrLess作为ssp的比较类型:

typedef set<string*, StringPtrLess> StringPtrSet;

StringPtrSet ssp;

现在你的循环最后将做你想要它做的(也就是前面你使用*i代替**i所修正的问题):
for (StringPtrSet::const_iterator i = ssp.begin();	// 打印“Anteater”,
		i != ssp.end();			                        // “Lemur”
		++i)				                                // “Penguin”
	cout << **i << endl;			                // “Wombat”

 

注意到我写的是函数对象。你可能奇怪为什么必须特意创造一个仿函数类而不是简单地为set写一个比较函数。例如,你可能想试试:

bool stringPtrLess(const string* ps1,		// 将成为用于
			const string* ps2)		// 按字符串值
{						// 排序的string*指针
	return *ps1 < *ps2;			// 的比较函数
}

set<string*, stringPtrLess> ssp;			// 假设使用stringPtrLess作为ssp的比较函数将不能编译

这里的问题是每个set模板的第三个参数都是一种类型。令人遗憾的是,stringPtrLess不是一种类型,它是一个函数。这就是为什么尝试使用stringPtrLess作为set的比较函数不能编译的原因,set不要一个函数,它要的是能在内部用实例化建立函数的一种类型。

无论何时你建立指针的关联容器,注意你也得指定容器的比较类型。大多数时候,你的比较类型只是解引用指针并比较所指向的对象(就像上面的StringPtrLess做的那样)。鉴于这种情况,你手头最好也能有一个用于那种比较的仿函数模板。像这样:

struct DereferenceLess {
	template <typename PtrType>
		bool operator()(PtrType pT1,		// 参数是值传递的,
				PtrType pT2) const		// 因为我们希望它们
		{					                // 是(或行为像)指针
			return *pT1 < *pT2;
		}
};

这样的模板消除了写像StringPtrLess那样的类的需要,因为我们可以改为使用DereferenceLess:记住这个指针不可以是void*,你可以写一个针对void*的特化:

 

struct DereferenceLess {
	template <typename PtrType>
		bool operator()(PtrType pT1,		// 参数是值传递的,
				PtrType pT2) const		// 因为我们希望它们
		{					                // 是(或行为像)指针
			return *pT1 < *pT2;
		}
    		bool operator()(void* p1,	 void* pT2) const		// 因为我们希望它们
		{					                // 是(或行为像)指针
			assert(false);
		}
};
set<string*, DereferenceLess> ssp;

 

噢,还有一件事。本条款是关于指针的关联容器,但它也可以应用于表现为指针的对象的容器,例如,智能指针和迭代器。如果你有一个智能指针或迭代器的关联容器,那也得为它指定比较类型。幸运的是,指针的这个解决方案也可以用于类似指针的对象。正如DereferenceLess适合作为T*的关联容器的比较类型一样,它也可以作为T对象的迭代器和智能指针容器的比较类型。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值