shared_ptr的unordered_set没有找到它存储的等效对象

mab*_*ble 1 c++ c++17

我有一个存储std::vector东西的课程.在我的计划,我创建std::unordered_setstd::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

Tob*_*ght 8

您无法修改集合的元素(并期望该集合起作用).因为你已经提供FooHashFooEq检查了所指对象的价值,所以从集合的角度来看,这就是价值的指称部分!

如果我们插入之前更改初始化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指针) .