一组weak_ptr

7
这里是代码:
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

}

我想知道如何计算/查找存储在intset中的weak_ptr,是否有更好的方法来完成相同的工作?

size() 不够用了吗? - gsamaras
@gsamaras size()可以给出总数,但我想知道是否存在特定的一个。而intset.count(make_shared<int>(1))并没有像我预期的那样工作。我以为它应该返回1。 - Kay
weak_ptr 的本质是它不持有所指对象的所有权,而只持有一个共同控制块的所有权。使用 make_shared 时,该控制块与对象在同一连续内存区域中,但这并不重要:当没有真正的 shared_ptr 引用该对象时,该对象在逻辑上已经不存在了。 - Cheers and hth. - Alf
lhs.lock() 可以返回一个空的 std::shared_ptr - Galik
5个回答

5

您不能将临时shared_ptr插入到弱指针集合中,因为这是内存泄漏的一种形式,即存储的弱指针指向已经被删除的内存。

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

这就是为什么你在集合中找不到它的原因 - 因为*lhs.lock()在此处是未定义行为。
请参见weak_ptr::lock文档
您需要按照以下方式制定您的òrder运算符:
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;
    }
};

这意味着,您需要在某处拥有此 shared_ptr 以对其进行计数:

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
}

有了以上内容,您的计数将正常工作。

还要考虑将shared_ptr保存在集合中...

[更新]

评论中的marko指出了一个有效的问题。std::weak_ptr不能以您使用的方式作为键。只有当您可以确保指向的值永远不会改变,指针本身永远不会过期时才能使用它。请参阅此示例:

    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

另一个例子:

    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

你可以使用std::shared_ptr来防止指针过期- std::shared_ptr具有operator < -但是此运算符比较指针本身,而不是指向的值- 因此更好的方法是std :: set<std :: shared_ptr<int>> - 但最好的方法是std :: set & lt; int & gt; 。或者将std :: set & lt; ... & gt;更改为std :: vector & lt; std :: weak_ptr & lt; int & gt; & gt; - 然后使用count_if - 参见:
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; 
                                  }); 

或者使用 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; 
                                  }); 

3
即使对lex_compare进行了修改,这仍然违反了std::set所假设的严格弱序,而且键值(通过lex_compare有效访问)被假定为不可变的——这在使用std::weak_ptr时是不可能的。结果会出现重复的键值和破坏键顺序。预计后续调用find()将产生虚假结果!您也不能使用multi_set来解决问题,因为缺乏键的不变性。 - marko
2
std::weak_ptr缺少operator<() - 这就是你需要提供自己的比较器的非常好的原因。 - marko
@marko - 我理解你的观点。存在于 std::weak_ptr 集合中的元素可能会过期或更改其指向的值,因此它在由 lex_compare 定义的排序中的位置会发生变化,但 std::set 仍将其放置在先前的位置... - PiotrNycz

3

不要试图编写自己的弱指针比较函数并得出行为不当的解决方案,我们可以使用标准的std::owner_less<>


1

shared_ptr不实现享元模式,而你似乎认为它实现了。

make_shared返回一个可以共享的指针。要获取对同一对象的更多指针,必须使用复制构造函数或复制赋值运算符,并传递现有的shared_ptr

make_shared不会创建现有对象的附加指针。它会创建一个新对象。

因此,intset.count(make_shared<int>(1))返回0是正确的。刚创建的shared_ptr<int>在集合中不存在。

连续调用make_shared<int>(1)不相等。

然后你还需要注意比较函数的问题。虽然存在可用于weak_ptr的排序方式,但你所使用的不是这种方式。不稳定的比较函数会导致set表现不佳。

你应该使用简单的set<int>


-1
将weak_pointer包装在一个容器中
例如:
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();}
};

并将其用作:

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

使用示例:

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();
}

-1

如果您没有实现清理方法并且定期使用它,那么拥有一组弱引用指针是一个糟糕的想法。您可能希望在比较函数中加入保护措施,因为目前结果未定义。例如,采纳@PiotrNycz的建议:

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());
    }
};

计算有效的弱指针

使用count_ifweak_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(); } 
    );
}

查找特定值的元素

您可以使用静态共享指针来存储查询(虽然这有点丑陋):

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);
}

还有一个例子:

#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";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接