1. STL容器排序
STL容器排序主要是在一些关联容器,比如map
或multimap
,优先队列上,这些容器内部对元素是有序的,在新增/删除元素或者初始化时需要使用排序规则来构造数据,实际上我们可以选择两种排序方式:
- 直接自定义排序规则
这个自定义排序规则可以是一个函数或者仿函数,但是Effecitve STL
中推荐使用仿函数,这主要可能是因为对于仿函数,容器内部可以默认初始化,而对于函数,需要在构造函数中传入函数对象,直接指定函数(指针)类型不会让容器默认初始化这个排序规则Compare
,这也就是类类型和非类类型最大的区别之一!类类型如果有无参构造函数,直接在定义时就可以初始化,非类类型则不行,因此最好还是传入仿函数类型,当然使用lambda也是一样的。
// 仿函数
class Cmp {
bool operator() (const int& a, const int& b) const {
return a < b;
}
};
// 真实函数
bool cmp(const int& a, const int& b) {
return a < b;
}
// 注意初始化方式区别,使用真实函数必须传入函数指针
std::map<int, int, Cmp> mapWithFunctor{};
std::map<int, int, decltype(&cmp)> mapWithFunction(cmp);
- 在自定义类型内重载关系运算符
这种方式直接在自定义类型内重载<
或>
运算符,并没有自定义排序规则,当然这就可以直接使用std提供的less
或greater
谓词规则,直接传入自定义类型即可,因为默认谓词内部就是一个对象的关系表达式return a < b
,那么直接重载对象类型的关系运算符即可。或者再自定义排序规则也可。但是有一个问题就是,对于非自定义类型没办法这样做,没有什么通用性。
class Obj {
public:
int a;
bool operator < (const Obj& obj) {
return a < obj.a;
}
bool operator > (const Obj& obj) {
return a > obj.a;
}
};
2. 最佳实践
上面已经说过,相比于直接在自定义类型中重载关系运算符,更通用的方法是自定义排序规则,当然,我们不可能对每一个类型都写一个自定义排序规则,这个时候就需要模版,而且模版也有讲究,我们加模版的地方是重载的
()
运算符函数上,而不是仿函数类上,这样在传入排序规则时不需要指定元素类型,编译器会在调用仿函数时再去实例化()
运算符,另外,对于元素类型为指针的情况,我们必须这样做,否则容器内部比较的是指针的大小,而我们希望的是比较指针指向对象的大小。
为了最大化通用性,可以同时处理指针和非指针类型,写一个仿函数如下:
struct Compare {
template<typename T>
bool operator() (const T& obj1, const T& obj2) const {
return obj1 < obj2;
}
template<typename Ptr>
bool operator() (Ptr* ptr1, Ptr* ptr2) const {
return *ptr1 < *ptr2;
}
};
直接将该仿函数类型传入容器即可:
std::map<std::string*, std::string, Compare> map1;
这里先复习一下,函数模版重载的匹配规则:第一步,尝试找到所有匹配成功的函数版本(对于函数模版则进行替换);第二步,匹配参数,找出需要实参->形参类型转换次数(注意引用不参与)最少的函数版本;第三步,优先选择上述查找结果中的非模版函数,如果没有,则选择更具体的版本,比如多了一个* 来说明形参类型是指针;第四步,如果查找结果为空,那么报错,如果查找结果有多个,则报歧义,只有找到有且仅有一个实现的时候才查找成功。
那么这里,如果(键)元素是指针类型T*, 第一步找到了T* const&
(第一个模版)和T*
;第二步匹配参数时只选择第二个,因为第一个还得转一次const
;匹配结束。
而对于非指针类型,第二个模版根本就无法替换成功,只能选择第一个。
但是如果我们将第二个模版的形参加上一个const会怎样,那么匹配到的就是第一个版本了,因为const T* 相比于T* const,肯定是T* const更匹配。但是const加在后面就没问题,因为同样是T const,第二个模版更具体,在第三步被选择。但是事实上,如果第一个模版不加const,第二个版本加后置const(const指针),相比于第一个版本的T*,选择的仍然是第二个版本T* const,这可能是因为T* const在第二步没被过滤,到了第三步,然后被更特化规则所选择。(具体原因需要进一步探讨)