C++元素排序:结构体与函数式比较器的统一实现疑问
std::sort和priority_queue的比较器方案 嘿,这个问题问得非常到位!你已经精准抓住了C++标准库中两个常用组件对比较器的不同要求——std::sort和std::priority_queue确实对比较器的形式有差异,但完全不需要重复定义两种版本,有几种通用方案可以解决这个问题。
先搞懂核心差异
要解决问题,得先明白两者对比较器的要求:
std::sort需要的是可调用对象:可以是函数指针、仿函数实例、lambda等,它会直接调用这个对象来对比元素;std::priority_queue的第三个模板参数是比较器类型:不是具体的实例,而是一个类型(比如结构体类型),容器会默认构造该类型的实例来完成比较。
你之前踩的坑,本质就是搞混了“传类型”和“传实例”的区别。
方案1:统一用仿函数(最稳妥通用)
这是兼容性最好的方式,能同时适配两个组件,写法也清晰:
定义仿函数
struct road { int from, len; }; // 按from升序的仿函数 struct cmpFrom { // 建议加const,保证调用时不修改对象,更符合规范 bool operator()(const road& a, const road& b) const { return a.from < b.from; } }; // 按len升序的仿函数(注意priority_queue的逻辑坑!) struct cmpLen { bool operator()(const road& a, const road& b) const { // 这里要返回a.len > b.len,而不是<! // 因为priority_queue是大顶堆,默认用less<T>:如果comp(a,b)为true,b会排在a前面 // 要实现小顶堆(len升序),就要让大的元素被"放到后面",所以返回a.len > b.len return a.len > b.len; } };
分别适配两个组件
// 给std::sort用:传入仿函数的实例(临时对象即可) std::sort(trips, trips + nbRoads, cmpFrom{}); // C++11及以后用{},也可以写cmpFrom() // 给priority_queue用:传入仿函数的类型,容器会自动构造实例 std::priority_queue<road, std::vector<road>, cmpLen> pickRoad;
你之前给std::sort传&cmpFrom是错误的,因为cmpFrom是类型,取类型的地址没有意义,应该传它的实例。
方案2:用Lambda表达式(C++11+,更简洁)
如果不想写结构体,Lambda是更轻量的选择,同样能适配两者:
适配std::sort
直接传递Lambda即可:
std::sort(trips, trips + nbRoads, [](const road& a, const road& b) { return a.from < b.from; });
适配priority_queue
Lambda的类型是匿名的,所以需要用decltype获取它的类型,并且构造时传入Lambda实例:
// 先定义Lambda auto cmpLen = [](const road& a, const road& b) { return a.len > b.len; // 同样注意这里的逻辑 }; // 用decltype获取Lambda类型,构造时传入实例 std::priority_queue<road, std::vector<road>, decltype(cmpLen)> pickRoad(cmpLen);
注:C++20以后,无捕获的Lambda支持默认构造,这时候可以不用传实例,直接写
std::priority_queue<road, std::vector<road>, decltype(cmpLen)> pickRoad;
为什么普通函数不能直接适配priority_queue?
std::priority_queue的第三个模板参数要求是类型,而普通函数的名字是函数指针(不是类型)。虽然可以通过指定函数指针类型来适配,但写法繁琐且不直观:
bool cmpLen(const road& a, const road& b) { return a.len > b.len; } // 可行但不推荐的写法:指定函数指针类型 std::priority_queue<road, std::vector<road>, bool(*)(const road&, const road&)> pickRoad(&cmpLen);
这种方式不仅代码冗余,而且如果函数签名变化,还要修改模板参数,远不如仿函数灵活。
总结
完全不需要重复定义比较器!统一用仿函数是最稳妥、兼容性最好的方案,既能适配std::sort(传实例),又能适配std::priority_queue(传类型)。Lambda表达式则是C++11以后更简洁的选择,适合临时的比较逻辑。
内容的提问来源于stack exchange,提问作者Neven V.

