我有一组3个整数的元组,我不想要任何重复。也就是说,我不希望2个条目具有相同的3个值。
这是我的代码。
struct Key{
unsigned a;
unsigned b;
unsigned c;
public:
Key(unsigned _a, unsigned _b, unsigned _c) :
a(_a),
b(_b),
c(_c) {}
bool operator<(const Key& rhs) const
{
if (a < rhs.a) {
return true;
}
if (b < rhs.b) {
return true;
}
if (c < rhs.c) {
return true;
}
return false;
};
};
std::set<Key> myset;
但我有时会在myset
中看到重复的内容。我无法准确捕捉到什么序列导致添加重复条目。它并不总是发生。
我的问题是,我的operator<
功能有什么本质上的错误吗?
答案 0 :(得分:35)
这几乎是正确的!但是你太快了。
bool operator<(const Key& rhs) const
{
if (a < rhs.a)
return true;
if (a > rhs.a)
return false;
if (b < rhs.b)
return true;
if (b > rhs.b)
return false;
return (c < rhs.c);
};
否则,例如,以下结果会给出错误的结果:
Key lhs{3,1,0};
Key rhs{2,2,0};
assert(lhs < rhs); // passes, wrongly, because !(3 < 2) but then (1 < 2).
// you need an immediate `return false` when !(3 < 2)
这样做更安全:
bool operator<(const Key& rhs) const
{
return std::tie(a, b, c) < std::tie(rhs.a, rhs.b, rhs.c);
}
C ++的标准库已经知道如何处理,所以你不必这样做。
现在,您的错误如何导致set
中的重复键?
Set的内部算法依赖于排序是一个严格的弱排序 - 当你打破这个前提条件时,你打破了管理内部树的算法,内部树是使用这个排序构造和排列的作为它的圣经。
基本上,所有地狱都破裂了。你可以从中获得崩溃。在您的情况下,症状更加温和(至少目前为止),变形/损坏的数据树导致重复数据的外观。如果您通过打破先决条件并导致UB开始,尝试推断导致特定结果的特定事件链是愚蠢的。
答案 1 :(得分:9)
您的operator<()
不一致,因为key1<key2
和key2<key1
可能都是true
(例如:key1={1,3,0}
,key2={3,1,0}
)。您应该为成员变量赋予优先权:
if (a < rhs.a) {
return true;
} else if (a == rhs.a) {
if (b < rhs.b) {
return true;
} else if (b == rhs.b) {
if (c < rhs.c) {
return true;
}
}
}
return false;
答案 2 :(得分:2)
您确实可以使用标准类std::tuple
作为密钥。
然而,可以通过以下方式定义运算符
bool operator <( const Key &rhs ) const
{
return
( a < rhs.a ) ||
( !( rhs.a < a ) && ( b < rhs.b ) ) ||
( !( rhs.a < a ) && !( rhs.b < b ) && ( c < rhs.c ) );
};
这个操作符可以满足你所需要的是对于a,b和c类的对象,将定义operator <
当然对于算术类型它已经定义了。
实际上它与
相同#include <tuple>
//...
bool operator <( const Key &rhs ) const
{
return std::tie( a, b, c ) < std::tie( rhs.a, rhs.b, rhs.c );
}