您的位置:首页 > 其它

为指针的关联容器指定比较类型

2016-12-12 20:24 260 查看
一、现象描述

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

set<string*> ssp;                                               

ssp.insert(new string("Anteater"));

ssp.insert(new string("Wombat"));

ssp.insert(new string("Lemur"));

ssp.insert(new string("Penguin"));

// ssp = “set of string ptrs”

然后你写了下列代码打印set的内容,希望字符串按字母顺序出现。毕竟,确定set保持它们的内容有序。

for (set<string*>::const_iterator i = ssp.begin(); i != ssp.end(); ++i)                         

cout << *i << endl;                            

// 你希望看到 这个:“Anteater”,“Lemur”,“Penguin”,“Wombat

注释描述了你希望看见的,但你根本没看见。取而代之的是,你看见四个十六进制的数。它们是指针的值。因为set容纳指针,*i不是一个string,它是一个string的指针。

如果你已经改为调用copy算法,

copy(ssp.begin(), ssp.end(),  ostream_iterator<string>(cout, "\n"));                            

// 把ssp中的字符串 拷贝到cout(但这不能编译)

你将不仅打更少的字符,而且你将很快会查明你的错误,因为这个copy的调用将不能编译,ostream_iterator需要知道被打印的对象的类型,所以当你告诉它是一个string时(通过作为模板参数传递),编译器检测到那和ssp中储存的对象类型(是string*)之间不匹配,它们会拒绝编译代码。获得了额外的类型安全。

如果你愤怒地把显式循环中的*i改为**i,你可能可以得到你想要的输出,但也可能不。是的,动物名字将被打印,但它们按字母顺序出现的机会只是24份之1。ssp保持它的内容有序,但是它容纳的是指针,所以它以指针的值排序,而不以string值。对于四个指针值可能有24种排列(译注:4! = 4 * 3 * 2 * 1 = 24),所以指针被条款20:为指针的关联容器指定比较类型储存时有24种可能的顺序。因此你看见字符串按字母排序有24份之1的几率。

二、解决方法

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

set<string*> ssp; 

是这个的简写:

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

好,为了完全准确,它是

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

的简化,但是分配器在本条款里与我们无关,所以我们将忽略它们。

如果你想要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的比较类型:

// 建立字符串的集合,按照StringPtrLess定义的顺序排序:

//为指针的关联容器指定比较类型

typedef set<string*, StringPtrLess> StringPtrSet;

StringPtrSet ssp;                      

...                                             

// 和前面一样插入
// 同样四个字符串

现在你的循环最后将做你想要它做的(也就是前面你使用*i代替**i所修正的问题):

for (StringPtrSet::const_iterator i = ssp.begin(); i != ssp.end(); ++i)                            

        cout << **i << endl;                    

 // 打印“Anteater”,“Lemur”,“Penguin”, “Wombat”

如果你想要改为使用算法,你可以写一个知道怎么在打印string*指针之前对它们解引用的函数,然后和for_each联用那个函数:

// 把ps指向的对象打印到cout

void print(const string *ps) {                                              

        cout << *ps << endl;

}

// 在ssp中的每个元素上调用print

for_each(ssp.begin(), ssp.end(), print);              

                                              

或者你想象并写出了泛型的解引用仿函数类,然后让它和transform与ostream_iterator连用:

// 当本类型的仿函数被传入一个T*时,它们返回一个const T&

struct Dereference {

        template <typename T>

        const T& operator()(const T *ptr) const{

                return *ptr;

        }

};

 // 通过解引用“转换” ssp中的每个元素,把结果写入cout

transform(ssp.begin(), ssp.end(), ostream_iterator<string>(cout,"\n"),Dereference());                        

三、总结

用算法代替循环不是要点,至少对于本条款来说是这样的。要点是无论何时你建立一个指针的标准关联容器,你必须记住容器会以指针的值排序。这基本上不是你想要的,所以你几乎总是需要建立自己的仿函数类作为比较类型。

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

// 将成为用于按字符串值排序的string*指针的比较函数

bool stringPtrLess(const string* ps1,const string* ps2){                                              

        return *ps1 < *ps2;                    

}

 // 假设使用stringPtrLess作为ssp的比较函数,这不能编译

set<string*, 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:

// 为指针的关联容器指定比较类型

set<string*, DereferenceLess> ssp;              

// set<string*, StringPtrLess>

噢,还有一件事。本条款是关于指针的关联容器,但它也可以应用于表现为指针的对象的容器,例如,智能指针和迭代器。如果你有一个智能指针或迭代器的关联容器,那也得为它指定比较类型。幸运的是,指针的这个解决方案也可以用于类似指针的对象。正如DereferenceLess适合作为T*的关联容器的比较类型一样,它也可以作为T对象的迭代器和智能指针容器的比较类型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: