如何为存储指针的C++ STL set重载<运算符及正确使用
为什么你的代码输出"found"?
你代码里的set<Node*>默认使用std::less<Node*>作为比较器,它比较的是指针本身的内存地址,而非指针指向的Node对象。你插入的是&n1,后续find(&n1)查找的是同一个内存地址的指针,set能直接匹配到,因此输出"found"。
另外你在Node类里重载的operator<完全没被调用——因为set根本没去解引用指针访问对象,它只关心指针的数值(地址),不关心指向的内容。
为什么存储指针时类的operator<不生效?
当set的元素类型是指针(比如Node*),默认比较器std::less<T>的作用是比较指针的数值(内存地址),而非调用指针指向对象的operator<。这是因为C++的指针本身是可比较类型,默认逻辑就是地址比较。
如果想让set根据对象内容排序/查找,必须明确告诉它不要比较指针地址,而是比较指针指向的对象,这就需要自定义比较器。
如何正确使用存储指针的set?
核心是给set传入自定义比较器,让它比较指针指向的对象。最常用的方式是定义一个重载了operator()的结构体:
#include <iostream> #include <set> using namespace std; class Node { int x; public: Node(int x) : x(x) {} // 用初始化列表更规范 bool operator<(const Node& n) const { cout << "inside operator<" << endl; return this->x < n.x; } int getX() const { return x; } // 新增getter方便调试 }; // 自定义比较器:比较指针指向的Node对象 struct NodePtrComparator { bool operator()(const Node* a, const Node* b) const { // 假设插入的都是有效指针,实际使用时最好加非空判断 return *a < *b; // 调用Node类的operator< } }; int main() { set<Node*, NodePtrComparator> s; // 指定自定义比较器 Node n1(10); Node n2(11); Node n3(10); // 和n1的x值相同 s.insert(&n1); s.insert(&n2); s.insert(&n3); // 因n3和n1等价,set不会插入重复元素 // 遍历set,会按x从小到大排序 for (const auto& ptr : s) { cout << ptr->getX() << " "; } // 输出:10 11 // 查找x=10的节点(n3和n1等价,会找到set中已存在的n1指针) auto res = s.find(&n3); if (res != s.end()) { cout << "\nfound node with x=" << (*res)->getX() << endl; // 输出found node with x=10 } return 0; }
自定义比较器的工作原理
set的第三个模板参数是比较类型,默认是std::less<T>。当你传入NodePtrComparator,set会用这个类型的对象判断元素顺序:
- 每次需要比较两个指针
a和b时,set会调用NodePtrComparator()(a, b)(即你重载的operator()) - 在这个函数里,我们解引用指针,调用
Node类的operator<比较对象内容,这样set就会根据对象逻辑维护有序结构,而非指针地址。
如何正确重载<运算符?
自定义类的operator<需要满足以下要求:
- 必须是const成员函数:set中的元素会被视为const,因此你的
operator<必须能作用于const对象,否则编译报错。 - 参数用const引用:避免不必要的对象拷贝,提升效率。
- 满足严格弱序(Strict Weak Ordering):这是STL容器的硬性要求,规则包括:
- 任何对象
a,a < a必须为false(自反性) - 若
a < b为true,则b < a必须为false(不对称性) - 若
a < b且b < c,则a < c必须为true(传递性) - 若
a不小于b且b不小于a,则a和b被视为等价(set只会保留一个)
- 任何对象
你原来的Node::operator<就符合要求,因为它比较的是x的大小,天然满足严格弱序。
为什么set只用小于比较?
STL有序容器(如set、map)只依赖小于运算符维护结构,因为通过严格弱序可以推导出所有需要的比较逻辑:
a与b等价:! (a < b) && ! (b < a)a > b:b < aa <= b:! (b < a)a >= b:! (a < b)
这种设计只需要用户定义一个比较逻辑,就能满足容器的所有排序和查找需求,简化了接口设计。
内容的提问来源于stack exchange,提问作者Sergei Shumilin

