So I have code that uses std::weak_ptr
and maintains them in an std::set
, and that works just fine -- and has worked for the last 5 or 7 years. Recently I thought I'd fiddle with using them in an std::unordered_set
(well, actually in an f14::F14ValueSet
) and for that, I would need a hash of it. As of now, there is no std::hash<std::weak_ptr>
, so what should I do instead?
The answer seems to be "just hash the control block", as implied by this question and reply: Why was std::hash not defined for std::weak_ptr in C++0x? , but how do I get access to the control block? In glibc, it's located at __weak_ptr<>::_M_refcount._M_pi->
but that's private (and implementation specific). What else can I do?
One answer is "just wait": maybe someday there will be a standard owner_hash()
for std::weak_ptr
, but I'd prefer something available now.
Make your own augmented weak ptr.
It stores a hash value, and supports ==
based off owner_before()
.
You must make these from shared_ptr
s, as a weak ptr with no strong references cannot be hashed to match its owner; this could create two augmented weak ptrs that compare equal but hash differently.
template<class T>
struct my_weak_ptr {
// weak ptr API clone goes here. lock() etc.
// different ctor:
my_weak_ptr(std::shared_ptr<T>const& sp){
if(!sp) return;
ptr=sp;
hash = std::hash<T*>{}(sp.get());
}
std::size_t getHash()const{return hash;}
friend bool operator<(my_weak_ptr const& lhs, my_weak_ptr const& rhs){
return lhs.owner_before(rhs);
}
friend bool operator!=(my_weak_ptr const& lhs, my_weak_ptr const& rhs){
return lhs<rhs || rhs<lhs;
}
friend bool operator==(my_weak_ptr const& lhs, my_weak_ptr const& rhs){
return !(lhs!=rhs);
}
private:
std::weak_ptr<T> ptr;
std::size_t hash=0;
};
these have stable, sensible hashes. While a recycled object pointer results in a hash collision, so long as they don't share control blocks they won't be equal.
One warning: Use of aliasing constructor could result in pathological results. As equality is based on control block equality, not pointer value.
So I tried to implement - as proposed by Yakk - Adam Nevraumont in his answer to this question - an inheriting hashable weak-pointer impelementation including the public interface. Might anyone comment in case I got something wrong?
template<class T>
struct HashableWeakPointer : protected std::weak_ptr<T>
{
public:
// Hash class
class Hash
{
public:
size_t operator()(HashableWeakPointer const & hashableWeakPointer) const
{
return hashableWeakPointer.getHash();
}
};
// constructor
HashableWeakPointer(std::shared_ptr<T> const & sp)
: std::weak_ptr<T>(sp)
, hash(0)
{
if (static_cast<bool>(sp))
{
hash = std::hash<T*>{}(sp.get());
}
}
// weak_ptr-interface
void reset() noexcept
{
std::weak_ptr<T>::reset();
hash = 0;
}
void swap(HashableWeakPointer & r) noexcept
{
std::weak_ptr<T>::swap(r);
std::swap(hash, r.hash);
}
using std::weak_ptr<T>::use_count;
using std::weak_ptr<T>::expired;
using std::weak_ptr<T>::lock;
template< class Y >
bool owner_before( const HashableWeakPointer<Y>& other ) const noexcept
{
return std::weak_ptr<T>::owner_before(static_cast<std::weak_ptr<Y>>(other));
}
template< class Y >
bool owner_before( const std::shared_ptr<Y>& other ) const noexcept
{
return std::weak_ptr<T>::owner_before(other);
}
// hash-interface
std::size_t getHash() const noexcept
{
return hash;
}
// helper methods
// https://en.cppreference.com/w/cpp/memory/shared_ptr
// "The destructor of shared_ptr decrements the number of shared owners of the control block. If that counter
// reaches zero, the control block calls the destructor of the managed object. The control block does not
// deallocate itself until the std::weak_ptr counter reaches zero as well."
// So below comparisons should stay save even if all shared_ptrs to the managed instance were destroyed.
friend bool operator<(HashableWeakPointer const& lhs, HashableWeakPointer const& rhs)
{
return lhs.owner_before(rhs);
}
friend bool operator!=(HashableWeakPointer const& lhs, HashableWeakPointer const& rhs)
{
return lhs<rhs || rhs<lhs;
}
friend bool operator==(HashableWeakPointer const& lhs, HashableWeakPointer const& rhs)
{
return !(lhs!=rhs);
}
friend std::ostream & operator<<(std::ostream & os, const HashableWeakPointer& dt)
{
os << "<" << dt.lock().get() << "," << dt.hash << ">";
return os;
}
private:
std::size_t hash;
};
As for usage, following is a small sample code
#include <iostream>
#include <memory>
#include <unordered_map>
typedef unsigned KeyValueType;
typedef HashableWeakPointer<KeyValueType> KeyType;
typedef unsigned ValueType;
typedef std::unordered_map<KeyType, ValueType, KeyType::Hash> MapType;
int main()
{
std::shared_ptr<KeyValueType> sharedPointer = std::make_shared<KeyValueType>(17);
ValueType const value = 89;
MapType map;
std::pair<MapType::iterator,bool> const inserted = map.insert({sharedPointer, value});
if (not inserted.second)
{
std::cerr << "Element for value " << value << " already existed." << std::endl;
}
for (MapType::value_type const & entry : map )
{
std::cout << "Key:[" << entry.first << "] Value:[" << entry.second << "]" << std::endl;
}
return 0;
}
which outputs for me [with a 64-bit size_t
]:
Key:[<0x1ea4b2817f0,2105794893808>] Value:[89]
where one can see, that the value pointer is used for the hash key [2105794893808 = 0x1ea4b2817f0].
In addition I would like to add two comments to Yakk - Adam Nevraumont's answer - however I am not allowed to comment as of yet. So here it goes:
And as the control block of a weak_ptr
is not freed until the last referencing weak_ptr
[and shared_ptr
...] to that instance is destroyed, this should be save with regard to the cited statement - right?
But this is not a deficiency of your proposed solution, but rather a question of one's definition of what it means for two weak_ptr
s to be equal, isn't it?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.