简体   繁体   中英

A set of weak_ptr

Here is the code:

struct lex_compare {
    bool operator() (const weak_ptr<int> &lhs, const weak_ptr<int> &rhs)const {
        return *lhs.lock() < *rhs.lock();
    }
};

int main(){
    set<weak_ptr<int>,lex_compare> intset;
    intset.insert(make_shared<int>(1));

    cout << "intset size:" << intset.size() << endl; //1
    cout << "Does 1 exist?"<< intset.count(make_shared<int>(1))<<endl; // failed

}

I want to know how to count/find the weak_ptr<int> stored in intset and if a better method is available to do the same work?

You cannot insert temporary shared_ptr to set of weak pointers because it is memory leak in the sense that this stored weak pointer points to already deleted memory.

intset.insert(make_shared<int>(1)); 
// after this instruction shared_ptr destructor frees the memory

That is why you cannot find it in set - because *lhs.lock() is UB here.

See weak_ptr::lock doc .

You need to make your òrder operator in this way:

struct lex_compare {
    bool operator() (const weak_ptr<int> &lhs, const weak_ptr<int> &rhs)const {
        auto lptr = lhs.lock(), rptr = rhs.lock();
        if (!rptr) return false; // nothing after expired pointer 
        if (!lptr) return true;  // every not expired after expired pointer
        return *lptr < *rptr;
    }
};

All that means - you need to have this shared_ptr sowmewhere to count it:

int main(){
    set<weak_ptr<int>,lex_compare> intset;
    auto shared1 = make_shared<int>(1); 
    intset.insert(shared1);

    cout << "intset size:" << intset.size() << endl; //1
    cout << "Does 1 exist?"<< intset.count(make_shared<int>(1))<<endl; // failed
}

With the above - your count will work.

Consider also to keep shared_ptr in set...

[UPDATE]

marko in comments pointed the valid issue. std::weak_ptr cannot be used as a key in a way you are using it at all. Only if you can ensure that pointed value will never change nor pointer itself will never expire. See this example:

    set<weak_ptr<int>,lex_compare> intset;
    auto shared1 = make_shared<int>(1); 
    intset.insert(shared1);
    cout << "Does 1 exist?"<< intset.count(make_shared<int>(1))<<endl; // works
    shared1.reset();
    cout << "Does 1 exist?"<< intset.count(make_shared<int>(1))<<endl; // failed

And the other example:

    set<weak_ptr<int>,lex_compare> intset;
    auto shared1 = make_shared<int>(1); 
    intset.insert(shared1);
    cout << "Does 1 exist?"<< intset.count(make_shared<int>(1))<<endl; // works
    *shared1 = 2;
    cout << "Does 1 exist?"<< intset.count(make_shared<int>(1))<<endl; // failed

You can keep std::shared_ptr which prevents from out-of-set expiration of pointer - and std::shared_ptr has operator < - but this operator compares pointers themselves - not the pointed values - so better is std::set<std::shared_ptr<int>> - but the best would be std::set<int>

Or change std::set<...> --> std::vector<std::weak_ptr<int>> - and use count_if -- see:

vector<weak_ptr<int>> intset;
auto shared1 = make_shared<int>(1);
intset.push_back(shared1);
cout << "Does 1 exist?"<< count_if(begin(intset), end(intset), 
                                  [](auto&& elem) 
                                  { 
                                     auto ptr = elem.lock();
                                     return ptr && *ptr == 1; 
                                  }); 

Or with std::set<std::shared_ptr<int>> :

set<shared_ptr<int>> intset;
auto shared1 = make_shared<int>(1);
intset.insert(shared1);
// if you can ensure shared1 value will not change:
cout << "Does 1 exist?"<< intset.count(shared1);
// if not  - use count_if - the slower than std::count
cout << "Does 1 exist?"<< count_if(begin(intset), end(intset), 
                                  [](auto&& ptr) 
                                  { 
                                     return ptr && *ptr == 1; 
                                  }); 

shared_ptr does NOT implement flyweight pattern, which you seem to assume.

make_shared returns a pointer that is capable of being shared. To get more pointers to the same object, you must use copy constructor or copy assignment operator, and pass the existing shared_ptr .

make_shared does NOT make an additional pointer to an existing object. It creates a new object.

Therefore, it is correct that intset.count(make_shared<int>(1)) returns 0 . The just-created shared_ptr<int> does not exist anywhere in the set.

Successive calls to make_shared<int>(1) do not compare equal.

Then there's the additional brokenness associated with your comparison function. There is an ordering that works for weak_ptr , but that is not it. Your unstable comparison function will cause set to badly misbehave.

You should use simply set<int> .

我们可以使用标准的std::owner_less<>代替尝试为弱指针编写自己的比较函数并提出行为不端的解决方案。

It's a bad idea to have a set of weak pointers unless you implement a cleanup method and use it regularly. You might want to put a safeguard in your comparison function because at present the result is undefined. Taking the advice from @PiotrNycz for example:

template <class T>
struct wptr_less_than 
{
    bool operator() ( const std::weak_ptr<T>& lhs, const std::weak_ptr<T>& rhs ) const {
        return lhs.expired() || (!rhs.expired() && *lhs.lock() < *rhs.lock());
    }
};

Counting valid weak pointers

Use a combination of count_if and weak_ptr::expired :

template <class T, class C, class A>
size_t count_valid_pointers( const std::set< std::weak_ptr<T>, C, A >& s )
{
    return s.size() - std::count_if( s.begin(), s.end(), 
         []( const std::weak_ptr<T>& wptr ){ return wptr.expired(); } 
    );
}

Finding an element with a specific value

You can use a static shared pointer to store the query (although this is kind of ugly):

template <class T, class C, class A>
typename std::set< std::weak_ptr<T>, C, A >::iterator
find_value( const std::set< std::weak_ptr<T>, C, A >& s, const T& val )
{
    static auto query = std::make_shared<T>();
    query.reset( const_cast<T*>(&val), []( T* ){} ) ;
    return s.find(query);
}

And an example:

#include <algorithm>
#include <iostream>
#include <memory>
#include <set>

template <class T>
struct wptr_less_than 
{
    bool operator() ( const std::weak_ptr<T>& lhs, const std::weak_ptr<T>& rhs ) const {
        return lhs.expired() || (!rhs.expired() && *lhs.lock() < *rhs.lock());
    }
};

template <class T, class C, class A>
size_t count_valid_pointers( const std::set< std::weak_ptr<T>, C, A >& s )
{
    return s.size() - std::count_if( s.begin(), s.end(), 
         []( const std::weak_ptr<T>& wptr ){ return wptr.expired(); } 
    );
}

template <class T, class C, class A>
typename std::set< std::weak_ptr<T>, C, A >::iterator
find_value( const std::set< std::weak_ptr<T>, C, A >& s, const T& val )
{
    static auto query = std::make_shared<T>();
    query.reset( const_cast<T*>(&val), []( T* ){} ) ;
    return s.find(query);
}


int main()
{
    std::set< std::weak_ptr<int>, wptr_less_than<int> > intset;

    auto a = std::make_shared<int>(1);
    auto b = std::make_shared<int>(2);

    intset.insert(a); intset.insert(b); a.reset();

    std::cout << "intset size:" << intset.size() << std::endl; //2
    std::cout << "intset real size:" << count_valid_pointers(intset) << std::endl; //1

    if ( find_value(intset,2) != intset.end() )
        std::cout << "Found it!\n";
}

wrap up the weak_pointer in a container

eg

template <class T> class WeakPtrAsKey {
  T * m_ptr;
    typedef std::shared_ptr<T> _Sptr;
    typedef std::weak_ptr<T> _Wptr;
    _Wptr m_wptr;
    public:
    WeakPtrAsKey():m_ptr(nullptr) {};
    WeakPtrAsKey(_Wptr wptr):m_ptr(wptr.lock().get()),m_wptr(wptr) {}
    WeakPtrAsKey(_Sptr sptr):m_ptr(sptr.get()),m_wptr(sptr) {}
    bool operator<(const WeakPtrAsKey<T> &other) const { return m_ptr<other.m_ptr;}
    bool operator==(const WeakPtrAsKey<T> &other) const { return m_ptr==other.m_ptr;}
    _Wptr getWeak() const { return m_wptr;}
    _Sptr lock() const { return m_wptr.lock();}
};

and use it as:

std::set<WeakPtrAsKey<MyKey>> mySet;
std::map<WeakPtrAsKey<MyKey>,MyVal> myMap;

usage examples:

void addToMap(std::weak_ptr<MyKey> key, const MyVal &val)
{
  myMap[key]=val
}
void addToMap(std::shared_ptr<MyKey> key, const MyVal &val)
{
  myMap[key]=val
}
std::shared_ptr<MyKey> getFirstKey()
{
  auto it=myMap.begin();
  return=it->first.lock();
}

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