【问题标题】:A set of weak_ptr一组weak_ptr
【发布时间】:2015-12-16 14:09:52
【问题描述】:

代码如下:

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

}

我想知道如何将count/find 存储在intset 中的weak_ptr&lt;int&gt; 以及是否有更好的方法来做同样的工作?

【问题讨论】:

  • size() 还不够吗?
  • @gsamaras size()给出了总金额,但我想知道是否存在特定的金额。 intset.count(make_shared&lt;int&gt;(1)) 并没有像我预期的那样工作。我认为它应该返回 1。
  • weak_ptr 的本质是它不拥有被引用对象的所有权。它拥有一个公共控制块的所有权,仅此而已。使用make_shared,该控制块与对象位于同一连续的内存区域中,但这并不重要:对象在逻辑上消失了,因为您没有更多真实的shared_ptr 引用它。
  • lhs.lock() 可以返回一个空的std::shared_ptr..

标签: c++ c++11 set smart-pointers weak-ptr


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      您不能将临时 shared_ptr 插入一组弱指针,因为从这个存储的弱指针指向已删除的内存的意义上讲,这是内存泄漏。

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

      这就是为什么你在 set 中找不到它 - 因为 *lhs.lock() 在这里是 UB。

      weak_ptr::lock doc

      您需要以这种方式制作您的 ò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 sowmewhere 来计算它:

      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 保留在集合中...

      [更新]

      cmets 中的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 &lt; - 但此运算符比较指针本身 - 而不是指向的值 - 所以更好的是 std::set&lt;std::shared_ptr&lt;int&gt;&gt; - 但最好的是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&lt;std::shared_ptr&lt;int&gt;&gt;:

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

      【讨论】:

      • 即使对上面的lex_compare进行了修改,这也违反了std::set假设的严格弱排序,此外,假设键值(通过lex_compare有效访问)是不可变的 - std::weak_ptr 不可能是这种情况。结果将是重复的键值和键顺序的中断。预计随后致电find() 会给出虚假结果!由于缺少密钥不变性,您也无法使用multi_set 来解决您的问题。
      • std::weak_ptr 缺少 operator&lt;() - 这就是为什么您需要提供自己的比较器 - 有一个很好的理由,就是这样。
      • @marko - 我明白你的意思。在 set std::weak_ptr 中可能会过期或更改其指向的值 - 因此它在 lex_compare 定义的顺序中的位置会发生变化 - 但 std::set 会将它放在以前的位置....
      【解决方案4】:

      除非您实现清理方法并定期使用它,否则拥有一组弱指针是个坏主意。您可能希望在比较函数中添加保护措施,因为目前结果未定义。以@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";
      }
      

      【讨论】:

        【解决方案5】:

        shared_ptr 没有实现你认为的享元模式。

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

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

        因此,intset.count(make_shared&lt;int&gt;(1)) 返回0 是正确的。刚刚创建的shared_ptr&lt;int&gt; 在集合中的任何位置都不存在。

        make_shared&lt;int&gt;(1) 的连续调用不相等。

        还有与您的比较功能相关的额外损坏。有一个适用于weak_ptr 的排序,但事实并非如此。你不稳定的比较函数会导致set行为不端。

        你应该简单地使用set&lt;int&gt;

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-07-25
          • 1970-01-01
          • 2022-01-16
          • 2016-10-18
          • 1970-01-01
          • 2011-08-29
          • 2020-08-26
          • 1970-01-01
          相关资源
          最近更新 更多