我有一个存储std::vector东西的课程.在我的计划,我创建std::unordered_set的std::shared_ptr这个类的对象(见下面的代码).我定义了自定义函数来计算哈希值和相等性,以便unordered_set"使用"对象而不是指针.这意味着:对于具有相同内容的不同对象的两个不同指针应被视为相等,我们称之为"等效".
到目前为止,一切都按预期工作,但现在我偶然发现了一个奇怪的行为:我添加一个指向对象的指针,unordered_set并创建一个指向具有相同内容的不同对象的不同指针.如上所述,我希望它my_set.find(different_object)会将有效的迭代器返回到存储在集合中的等效指针.但事实并非如此.
这是一个最小的工作代码示例.
#include <boost/functional/hash.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <unordered_set>
#include <vector>
class Foo {
public:
Foo() {}
bool operator==(Foo const & rhs) const {
return bar == rhs.bar;
}
std::vector<int> bar;
};
struct FooHash {
size_t operator()(std::shared_ptr<Foo> const & foo) const {
size_t seed = 0;
for (size_t i = 0; i < foo->bar.size(); ++i) {
boost::hash_combine(seed, foo->bar[i]);
}
return seed;
}
};
struct FooEq {
bool operator()(std::shared_ptr<Foo> const & rhs,
std::shared_ptr<Foo> const & lhs) const {
return *lhs == *rhs;
}
};
int main() {
std::unordered_set<std::shared_ptr<Foo>, FooHash, FooEq> fooSet;
auto empl = fooSet.emplace(std::make_shared<Foo>());
(*(empl.first))->bar.emplace_back(0);
auto baz = std::make_shared<Foo>();
baz->bar.emplace_back(0);
auto eqFun = fooSet.key_eq();
auto hashFun = fooSet.hash_function();
if (**fooSet.begin() == *baz) {
std::cout << "Objects equal" << std::endl;
}
if (eqFun(*fooSet.begin(), baz)) {
std::cout << "Keys equal" << std::endl;
}
if (hashFun(*fooSet.begin()) == hashFun(baz)) {
std::cout << "Hashes equal" << std::endl;
}
if (fooSet.find(baz) != fooSet.end()) {
std::cout << "Baz in fooSet" << std::endl;
} else {
std::cout << "Baz not in fooSet" << std::endl;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
产量
Objects equal
Keys equal
Hashes equal
Run Code Online (Sandbox Code Playgroud)
这是问题所在:
Baz not in fooSet
Run Code Online (Sandbox Code Playgroud)
我在这里错过了什么?为什么集合找不到等价的对象?
可能有兴趣:我玩弄了这个,发现如果我的班级存的是平原int而不是a std::vector,它就可以了.如果我坚持std::vector但改变我的构造函数
Foo(int i) : bar{i} {}
Run Code Online (Sandbox Code Playgroud)
并初始化我的对象
std::make_shared<Foo>(0);
Run Code Online (Sandbox Code Playgroud)
它也有效.如果我删除整个指针的东西,它会作为std::unordered_set::find返回常量迭代器而中断,因此无法修改集合中的对象(这种方式).但是,无论如何,这些变化都不适用于我的真实计划.
我使用g++版本7.3.0 编译-std=c++17
您无法修改集合的元素(并期望该集合起作用).因为你已经提供FooHash并FooEq检查了所指对象的价值,所以从集合的角度来看,这就是价值的指称部分!
如果我们在插入之前更改初始化fooSet以设置元素,我们会得到您想要/期望的结果:
std::unordered_set<std::shared_ptr<Foo>, FooHash, FooEq> fooSet;
auto e = std::make_shared<Foo>();
e->bar.emplace_back(0); // modification is _before_
fooSet.insert(e); // insertion
Run Code Online (Sandbox Code Playgroud)
查找集合中的对象取决于散列值不变.如果我们确实需要在添加成员后修改成员,我们需要将其删除,进行更改,然后添加修改后的对象 - 请参阅Yakk的回答.
为了避免遇到这样的问题,使用std::shared_ptr<const Foo>as作为元素可能会更安全,这将阻止Foo通过集合修改指向(虽然你仍然负责使用你可能也有的任何非const指针) .