简体   繁体   中英

How to compute hash of std::weak_ptr?

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:

  1. "While a recycled object pointer results in a hash collision, so long as they don't share control blocks they won't be equal."

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?

  1. "One warning: Use of aliasing constructor could result in pathological results. As equality is based on control block equality, not pointer value."

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM