У меня есть класс, в котором хранится 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;
}
Вывод
Objects equal
Keys equal
Hashes equal
И вот в чем проблема:
Baz not in fooSet
Чего мне здесь не хватает?Почему набор не находит эквивалентный объект?
Возможно, интерес: я поэкспериментировал с этим и обнаружил, что если мой класс хранит обычный int
вместо std::vector
, он работает.Если я придерживаюсь std::vector
, но меняю свой конструктор на
Foo(int i) : bar{i} {}
и инициализирую свои объекты с
std::make_shared<Foo>(0);
, это также работает.Если я удаляю весь указатель, он ломается, так как std::unordered_set::find
возвращает константы-итераторы, и, таким образом, модификация объектов в наборе невозможна (таким образом).Тем не менее, ни одно из этих изменений не применимо в моей реальной программе, так или иначе.
Я компилирую с g++
версии 7.3.0, используя -std=c++17