【问题标题】:How to make a c++11 std::unordered_set of std::weak_ptr如何制作 std::weak_ptr 的 c++11 std::unordered_set
【发布时间】:2012-11-21 15:36:12
【问题描述】:

我有这样一套: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>]’:

【问题讨论】:

  • "我有一个这样的集合:set, owner_less >> setName;" 你这是什么意思?
  • @jogojapan 谢谢我已经试过了,但没有帮助。
  • @JoachimPileborg 我只问了五个问题,而且只有一个问题得到了回答,而且是我自己回答的。所以这就是为什么我从来没有接受任何答案。谢谢提醒。
  • 你一共有八个问题,其中六个有答案。其中两个问题有多个答案。即使你自己写了答案,你仍然可以接受。

标签: c++ c++11 std unordered-set weak-ptr


【解决方案1】:

简短而不幸的答案是,虽然shared_ptr&lt;&gt; 可以安全地用作无序集合或映射中的键,但weak_ptr&lt;&gt; 不能也不能。再多的诡计也无法保证安全。

这是因为weak_ptr的接口不暴露对共享控件对象的访问,这是owner_before()在有序集合或映射中使用时比较的基础。

虽然锁定指针然后散列shared_ptr 似乎是合理的,但事实并非如此。如果最后一个shared_ptr 超出范围,则哈希值将更改,这将导致下次迭代您的集合或映射时出现未定义的行为。这很可能不会被注意到,直到您的代码在客户面前投入生产,您偶尔会遇到意外和莫名其妙的功能损失,但是您的单元测试仍然会完美地通过,让您错误地认为您的测试覆盖率很好,您的代码是可靠的,但应该归咎于用户、硬件或网络。

因此,总而言之,如果您要使用 weak_ptr's 来构建您的非拥有对象缓存(它们非常好),您需要使用 std::set&lt;weak_ptr&gt; 并遭受微不足道的性能损失(尽管在事实上,这与保护集合的mutex 造成的性能损失相形见绌)。

如果您真的想使用weak_ptr 作为无序键,您必须自己编写(提示:使用共享控制块的地址作为散列函数的基础)。

【讨论】:

  • 我正在考虑这个问题。鉴于weak_ptr 不提供对其内部状态的任何访问,这有点棘手。包含weak_ptr&lt;T&gt;const T* 的包装类怎么样,在构造时,T* 设置为weak.lock().get() 并用于散列,但相等性测试将与weak.lock() 进行比较。这样一来,T 可以被销毁,并且哈希不会改变,但相等会改变。
  • @Ben 这假定地址从不用于两个不同的对象两次。这不一定是真的。
  • 是的,如果它打算作为一个唯一的哈希,比如一个 sha1,我们假设哈希相等意味着对象相等,但它不应该在哈希表中正常工作(ish)吗?例如,您插入一个项目,指向的对象被破坏,您创建一个恰好位于同一内存位置的新对象,您插入它,它的哈希值相同但比较不相等,因为原始项目现在比较作为nullptr,所以添加了新的东西。我闻到了危险信号,但我不相信这种方法注定要失败。
  • @Ben 它注定要失败。如果您要包装weak_ptr,那么您需要为每个对象生成一个唯一标识。每个创建的weak_ptr 都需要知道unique_id。在这种情况下,为什么不直接使用 unique_id 作为 unordered_map 键?
  • 我看到了问题。如果标准提供了类似于owner_before 的哈希值会怎样?在我的实现中,std::weak_ptr&lt;T&gt;::owner_before(const weak_ptr&lt;U&gt;&amp; p) 只是{ return __cntrl_ &lt; p.__cntrl_; },其中__cntrl_ 是指向控制块的指针。因此,如果std::weak_ptr 提供了将__cntrl_ 转换为std::size_tstd::size_t unique_id() const 方法,我们可以关闭unique_id() 并对其进行哈希处理。
【解决方案2】:

我不认为建议的哈希函数是正确的。如果指向对象的所有共享指针都消失了,那么weak_ptr&lt;X&gt;::lock() 将返回空的 shared_ptr,其哈希值可能为零。所以哈希函数可以在整个时间返回不同的值。

我认为这里正确的解决方案是使用 boost::unordered_map&lt;X*, boost::weak_ptr&lt;X&gt;&gt;。类型X* 可以很容易地用作哈希映射的键,weak_ptr&lt;X&gt; 作为值让您有机会找出引用的对象是否仍然存在。

要将值存储到此哈希中,您可以使用类似的方法:

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

【讨论】:

  • 这使用了一个可能过期的对象的内存位置作为key,这意味着你需要考虑一个被插入的对象可能已经被分配在与之前相同的位置的可能性其键仍然存在于地图中的对象。
  • weka_ptr<...> 确定前一个对象是否仍然存在。如果它存在,则不能有具有相同地址的不同对象,如果不存在,则可以替换它。 Insert 不允许用相同的键插入 两次(如果你没有 multimap),所以上面的语句实际上应该检查插入是否成功并采取相应的行动。
【解决方案3】:

请阅读下面的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::hash 对 std::weak_ptr 的专门化,因此您必须为此提供自己的散列函数:

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);
   }
};

编辑: 您还需要提供一个相等函数,因为没有为 weak_ptr 提供 std::equal_to 。 从"Equality-compare std::weak_ptr" on Stackoverflow 采取一种可能的方式来做到这一点:

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;

【讨论】:

  • 是否有可能让 MyWeakPtrHash 接受一个 weak_ptr 然后把它变成一个 shared_ptr 并获取它的哈希值并返回它。我一直在尝试一些不同的东西,但没有任何东西可以编译。
  • 由于 std::hash 为 std::shared_ptr 提供了专门化,您可以利用它。我更新了示例以利用它。我不知道这是否会按预期工作......:|
  • 密钥的哈希值假定为常量,但您的函数允许对其进行修改:如果weak_ptr 过期,wp.lock() 将返回一个空的 shared_ptr,它具有不同的哈希值。这将导致未定义的行为。
  • 将其与T=void 一起使用会导致有关限定符被丢弃的错误。为什么?如何解决?
【解决方案4】:

这里给出了一个可行的解决方案:How to compute hash of 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?,解决这个问题的最新标准委员会草案在这里:JTC1 WG21 P1901

【讨论】:

    猜你喜欢
    • 2015-10-01
    • 2016-07-31
    • 2012-10-04
    • 2016-03-14
    • 2014-12-17
    • 2014-08-10
    • 2015-10-11
    • 1970-01-01
    • 2014-05-05
    相关资源
    最近更新 更多