如何在c++11中创建一个std::weak_ptr的std::unordered_set

lb3vh1jj  于 2022-11-19  发布在  其他
关注(0)|答案(4)|浏览(201)

我有这样一套:set<weak_ptr<Node>, owner_less<weak_ptr<Node> > > setName;
它工作得很好。但是我想把它改成一个无序的集合。但是,当我这样做的时候,我会得到大约六页的错误。有什么想法怎么做吗?
在浏览了所有页面的错误信息后,我发现了可能有帮助的行。

/usr/include/c++/4.7/bits/functional_hash.h:60:7: error: static assertion failed: std::hash is not specialized for this type
/usr/include/c++/4.7/bits/stl_function.h: In instantiation of ‘bool std::equal_to<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = std::weak_ptr<Node>]’:
64jmpszr

64jmpszr1#

简短而不幸的答案是,虽然shared_ptr<>可以安全地用作无序集合或Map中的键,但weak_ptr<>不能也不应该,再多的诡计也不能使它安全。
这是因为weak_ptr的接口不公开对共享控件对象的访问,这是在有序集或Map中使用owner_before()时进行比较的基础。
虽然锁定指针然后对shared_ptr进行哈希似乎是合理的,但实际上并非如此。如果最后一个shared_ptr超出了范围,哈希值将发生变化,这将导致下次迭代集合或Map时出现未定义的行为。这很可能不会被注意到,直到您的代码在生产环境中面对客户时,偶尔会出现意外和无法解释的功能丢失。但是您的单元测试仍然会完美地通过,这给了您一个错误的想法,即您的测试覆盖率很好,您的代码很可靠,应该受到责备的是用户、硬件或网络。
因此,总而言之,如果您打算使用weak_ptr来构建非拥有对象缓存(它们非常适合),则需要使用std::set<weak_ptr>,并且会受到极小的性能影响(尽管实际上这与保护集合的mutex所造成的性能损失相比微不足道)。
如果你真的想使用weak_ptr作为一个无序键,你必须自己写(提示:使用共享控制块的地址作为散列函数的基础)。

snz8szmq

snz8szmq2#

我不认为这个哈希函数是正确的,如果指向对象的所有共享指针都消失了,那么weak_ptr<X>::lock()将返回空的shared_ptr,其哈希值可能为零,因此哈希函数可以在不同的时间返回不同的值。
我认为这里正确的解决方案是使用boost::unordered_map<X*, boost::weak_ptr<X>>。类型X*可以很容易地用作哈希Map的键,而weak_ptr<X>作为值可以让你有机会找出被引用的对象是否仍然存在。
要将值存储到该散列中,可以使用以下代码:

if (boost::shared_ptr<X> p = wp.lock()) {
    // weak_ptr is still valid
    ptrs.insert(std::make_pair(p.get(), p));
}
iqxoj9l9

iqxoj9l93#

请阅读以下Richard Hodges的答案,因为我的答案是不正确的,尽管这是公认的解决方案。

由于unordered_sets是基于散列的,因此必须为std::weak_ptr数据类型提供散列function object
如果您看一下unordered_set模板参数

template<class Key,
    class Hash = std::hash<Key>,
    class Pred = std::equal_to<Key>,
    class Alloc = std::allocator<Key> >
    class unordered_set;

您会注意到std::unordered_set为您提供了一个默认的std::hash〈〉模板参数,但是由于std::hash只为specific set数据类型提供了专门化,因此您可能必须提供自己的参数。
你引用的错误消息告诉你,没有std::weak_ptr〈〉的std::hash〈〉专门化存在,所以你必须提供你自己的散列函数:

template<typename T>
struct MyWeakPtrHash : public std::unary_function<std::weak_ptr<T>, size_t> {
   size_t operator()(const std::weak_ptr<T>& wp)
   {
      // Example hash. Beware: As zneak remarked in the comments* to this post,
      // it is very possible that this may lead to undefined behaviour
      // since the hash of a key is assumed to be constant, but will change
      // when the weak_ptr expires
      auto sp = wp.lock();
      return std::hash<decltype(sp)>()(sp);
   }
};

**Edit:**您还需要提供一个等式函数,因为没有为weak_ptr提供std::equal_to。

template<typename T>
struct MyWeakPtrEqual : public std::unary_function<std::weak_ptr<T>, bool> {

   bool operator()(const std::weak_ptr<T>& left, const std::weak_ptr<T>& right)
   {
      return !left.owner_before(right) && !right.owner_before(left);
   }
};

所有这些加起来,我们得到以下结果:

std::unordered_set<std::weak_ptr<T>,
                   MyWeakPtrHash<T>,
                   MyWeakPtrEqual<T>> wpSet;
x6h2sr28

x6h2sr284#

工作解决方案如下:如何计算std::weak_ptr的散列?下面是一个稍微扩展的变体,它添加了缺少的细节。与前面给出的答案不同,这是因为散列是在shared_ptr计数降到零之前计算和存储的。

namespace foobar
{
// Public inheritance was used to avoid having to
// duplicate the rest of the API. Unfortunately this
// allows object slicing. So, an alternate solution is
// to use private inheritance, and `using` to provide
// the missing API.
template<class T>
struct hashable_weak_ptr : public std::weak_ptr<T>
{
   hashable_weak_ptr(std::shared_ptr<T>const& sp) :
      std::weak_ptr<T>(sp)
   {
      if (!sp) return;
      _hash = std::hash<T*>{}(sp.get());
   }

   std::size_t get_hash() const noexcept { return _hash; }

   // Define operator<() in order to construct operator==()
   // It might be more efficient to store the unhashed
   // pointer, and use that for equality compares...
   friend bool operator<(hashable_weak_ptr const& lhs,
                         hashable_weak_ptr const& rhs)
   {
      return lhs.owner_before(rhs);
   }
   friend bool operator!=(hashable_weak_ptr const& lhs,
                          hashable_weak_ptr const& rhs)
   {
      return lhs<rhs or rhs<lhs;
   }
   friend bool operator==(hashable_weak_ptr const& lhs,
                          hashable_weak_ptr const& rhs)
   {
      return not (lhs != rhs);
   }
   private:
      std::size_t _hash = 0;
};
} // namespace foobar

namespace std
{

// Specializations in std namespace needed
// for above to be usable.
template<class T>
struct owner_less<foobar::hashable_weak_ptr<T>>
{
   bool operator()(const foobar::hashable_weak_ptr<T>& lhs,
                   const foobar::hashable_weak_ptr<T>& rhs) const noexcept
   {
      return lhs.owner_before(rhs);
   }
};

template<class T>
struct hash<foobar::hashable_weak_ptr<T>>
{
   std::size_t operator()(const foobar::hashable_weak_ptr<T>& w) const noexcept
   {
      return w.get_hash();
   }
};
} // namespace std

这个问题的一个变体首先被问到这里:Why was std::hash not defined for std::weak_ptr in C++0x?和解决此问题的最新标准委员会草案如下:是的。

相关问题