如何制作一个c++11 std::unordereded_set的std::weak_ptr
How to make a c++11 std::unordered_set of std::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>]’:
简短而不幸的答案是,虽然shared_ptr<>
可以安全地用作无序集或映射中的键,但weak_ptr<>
不能也绝不能。再多的诡计也无法保证它的安全。
这是因为weak_ptr
的接口不公开对共享控制对象的访问,而共享控制对象是owner_before()
在有序集或映射中使用时进行比较的基础。
虽然锁定指针然后对shared_ptr
进行散列似乎是合理的,但事实并非如此。如果最后一个shared_ptr
超出范围,则哈希值将发生更改,这将导致下次迭代集合或映射时出现未定义的行为。这很可能会被忽视,直到你的代码在客户面前生产,偶尔会出现意外和莫名其妙的功能损失,但你的单元测试仍然会完美通过,让你误以为你的测试覆盖率很好,你的代码是可靠的,这是用户、硬件或网络的责任。
因此,总之,如果您要使用weak_ptr
来构建非拥有对象缓存(它们非常出色),则需要使用std::set<weak_ptr>
,并承受极小的性能损失(尽管在现实中,这与保护集的mutex
造成的性能损失相比相形见绌)。
如果您真的想使用weak_ptr
作为无序密钥,则必须编写自己的密钥(提示:使用共享控制块的地址作为哈希函数的基础)。
我认为建议的哈希函数不正确。如果指向对象的所有共享指针都消失了,那么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));
}
请阅读下面Richard Hodges的回答,因为我的回答是不正确的,尽管我的回答已被接受
由于unordered_sets
是基于哈希的,因此必须为std::weak_ptr数据类型提供一个哈希函数对象。
如果您查看未定义的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<gt;模板参数。但由于std::hash只为一组特定的数据类型提供专门化,您可能必须提供自己的数据类型。
您引用的错误消息告诉您,没有std::hash<gt;std::weak_ptr<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。从";相等比较std::weak_ptr";堆栈溢出:
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;
这里给出了一个有效的解决方案:如何计算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
这里首先提出了这个问题的一个变体:为什么在C++0x中没有为std::weak_ptr定义std::hash?解决这一问题的最新标准委员会草案如下:JTC1 WG21 P1901。
- 使用std::multimap迭代器创建std::list
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- 从持续时间构造std::chrono::system_clock::time_point
- std::具有相同基类的类的变体
- std::向量与传递值的动态数组
- 使用std::vector的OpenCL矩阵乘法
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- std::condition_variable::wait()如何评估给定的谓词
- 如何获取std::result_of函数的返回类型
- std::原子加载和存储都需要吗
- 将对象移动到std::shared_ptr
- POCO::PostgreSQL:如何将std::vector支持添加到`Binder::bind`
- 使用一个考虑到std::map中键值的滚动或换行的键
- 如何从 std::atomic 中提取指针 T<T>?
- 为什么 std::unique 不调用 std::sort?
- 使用std::函数映射对象方法
- 可组合的lambda/std::函数与std::可选