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].