[英]std::map with keys that have no ordering
如果我想要使用用戶定義的對象作為鍵的std::map
應該采用哪種方法?
讓我們考慮這個最小的虛假代碼(它可以編譯但不能正確運行):
#include <map>
class Object
{
public:
int x, y;
Object(int x, int y) : x(x), y(y) {};
bool operator<(const Object& o) const { return 1;}; // bogus just to make compiler happy
};
int main()
{
Object o1(1, 2), o2(3, 4);
std::map<Object, int> objmap;
objmap.insert(std::make_pair(o1, 11));
objmap.insert(std::make_pair(o2, 22));
}
為了能夠在std::map
使用對象作為鍵,我們需要一個<
操作符。
到目前為止一切都很好,但是如果不能用“更少”或“更大”來比較對象,而只能用“不同”來比較,那該怎么辦?
您的對象必須遵守嚴格的總順序。 這意味着,最重要的是,您的<
關系必須是非自反和可傳遞的。 在您的情況下,請考慮僅使用 std::tuple,它已經提供了一個具有字典順序的功能operator<
。
這將是“一個”正確的排序,但很可能根本不是您想要的:
friend bool operator<(Object const& a, Object const& b) {
return a.x < b.x; // probably not what you want, but legal
}
因為這意味着Object{1,100}
和Object{1,101}
會導致std::map
假設它們是相同的鍵(除非你想要那個)。
但是,如果您提供以下內容,您實際上可以打破地圖:
friend bool operator<(Object const& a, Object const& b) {
return a.x <= b.x; // this will break map
}
因為它不是非自反的(即 x < x 不會產生錯誤)。
通常,您必須考慮對象的所有屬性,以避免混疊(即 map 認為兩個不同的對象相等)。 除非您向我們展示您的Object
,否則無法向您展示如何實現正確的operator<
。
在大多數情況下, std::unordered_map
是更好(也更有效)的選擇。
到目前為止一切都很好,但是如果不能用“更少”或“更大”來比較對象,而只能用“不同”來比較,那該怎么辦?
您一般可合成排序,如
bool operator<(const Object& o) const { return std::tie(a, b) < std::tie(o.a, o.b); }
順便說一下,這種構造是 C++20 的默認比較將使用的。
auto operator<=>(const Object& o) const = default;
由於這里Object
之間沒有全序關系,一種可能性是使用unorded_map
而不是map
。
因此object
類至少需要一個重載==
運算符和一個自定義哈希函數:
#include <unordered_map>
#include <iostream>
class Object
{
public:
int x, y;
Object(int x, int y) : x(x), y(y) {};
bool operator==(const Object& o) const
{
std::cout << "equal hashes, using == operator on (" << x << "," << y << ")\n";
auto isequal = x == o.x && y == o.y;
std::cout << "objects are " << (isequal ? "" : "not ") << "equal\n";
return isequal;
};
friend std::ostream& operator<<(std::ostream& os, const Object& o);
};
std::ostream& operator<<(std::ostream& os, const Object & o)
{
os << "(" << o.x << "," << o.y << ")";
return os;
}
struct ObjectHash {
size_t operator()(const Object& o) const
{
std::cout << "hashing (" << o.x << "," << o.y << ")\n";;
return o.x + o.y; // purposly bad hash function, so we get collisions
// (o.x * 127) ^ o.y would be better, still not ideal
}
};
int main()
{
Object o1(1, 2);
Object o2(3, 4); // different from o1, o3 and o4
Object o3(1, 2); // equal to o1
Object o4(2, 1); // different from o1, o2 and o3 but same hash as o1
std::unordered_map<Object, int, ObjectHash> objmap;
std::cout << "Inserting " << o1 << "\n"; objmap.insert(std::make_pair(o1, 11));
std::cout << "Inserting " << o2 << "\n"; objmap.insert(std::make_pair(o2, 22));
std::cout << "Inserting " << o3 << "\n"; objmap.insert(std::make_pair(o3, 33));
std::cout << "Inserting " << o4 << "\n"; objmap.insert(std::make_pair(o4, 33));
}
std::cout
用於說明目的。
輸出:
Inserting (1,2)
hashing (1,2)
Inserting (3,4)
hashing (3,4)
Inserting (1,2)
hashing (1,2)
equal hashes, using == operator on (1,2)
objects are equal
Inserting (2,1)
hashing (2,1)
equal hashes, using == operator on (2,1)
objects are not equal
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.