如何制作std :: weak_ptr的c ++ 11 std :: unordered_set

时间:2012-12-04 03:30:42

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

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

4 个答案:

答案 0 :(得分:11)

简短而不幸的答案是,虽然shared_ptr<>可以安全地用作无序集或地图中的关键字,但weak_ptr<>不能也不能。没有多少诡计可以使它安全。

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

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

因此,总而言之,如果您要使用weak_pt来构建非拥有对象缓存(它们非常出色),则需要使用{{1}并且遭受微乎其微的性能损失(尽管实际上这会因保护集合的std::set<weak_ptr>导致的性能损失而相形见绌。)

如果你真的想使用mutex作为无序密钥,你必须自己编写(提示:使用共享控制块的地址作为哈希函数的基础)。

答案 1 :(得分:9)

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

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

要将值存储到此哈希,您可以使用以下内容:

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

答案 2 :(得分:3)

由于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&lt;&gt;模板参数。但由于std :: hash仅为specific set数据类型提供特化,因此您可能必须提供自己的数据类型。

您引用的错误消息告诉您,没有std :: hash&lt;&gt; std :: weak_ptr&lt;&gt;的专业化存在,所以你必须为此提供自己的散列函数:

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;

答案 3 :(得分:-1)

感谢lx,现在构建了以下代码。为了使用gcc 4.7.2构建它我不得不再添加两个'const',所以现在代码下面应该是将std :: weak_ptr放入std :: unordered_set的完整工作示例。

#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_set>
#include <functional>

using namespace std;

template<typename T>
struct MyWeakPtrHash : public std::unary_function<std::weak_ptr<T>, size_t> {
   size_t operator()(const std::weak_ptr<T>& wp) const
   {
      auto sp = wp.lock();
      return std::hash<decltype(sp)>()(sp);
   }
};

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) const
   {
      return !left.owner_before(right) && !right.owner_before(left);
   }
};

int main() {
    unordered_set<std::weak_ptr<string>, MyWeakPtrHash<string>,
                   MyWeakPtrEqual<string> > stringset;

    shared_ptr<string> shptr(new string("Hi there") );
    stringset.insert( shptr );
    cout << stringset.size() << endl;
}