如何在使用shared_ptr时检测周期

时间:2009-04-22 07:33:33

标签: c++ garbage-collection shared-ptr reference-counting

shared_ptr是Boost库中的引用计数智能指针。

引用计数的问题是它不能处理循环。我想知道如何用C ++解决这个问题。

请不要这样的建议:“不要制作周期”,或“使用weak_ptr”。

修改

我不喜欢只使用weak_ptr的建议,因为很明显,如果你知道你会创建一个循环,那么你就不会有问题。如果在运行时生成shared_ptrs,也无法知道编译时会有一个循环。

所以请自我删除使用weak_ptr的答案,因为我特别要求不要那些答案......

10 个答案:

答案 0 :(得分:25)

shared_ptr代表所有权关系。虽然weak_ptr代表意识。让几个对象彼此拥有意味着您遇到了架构问题,这可以通过将一个或多个自己的更改为意识到来解决(即{{1} }}的)。

我不明白为什么weak_ptr被认为是无用的。

答案 1 :(得分:17)

我理解你被告知要使用weak_ptr来打破循环引用和我自己的烦恼当我被告知循环引用是错误的编程风格时我几乎感到愤怒。

您具体询问如何发现循环引用。事实是,在一个复杂的项目中,一些参考周期是间接的,很难发现。

答案是你不应该做出虚假声明,使你容易受到循环引用的攻击。我很认真,我批评一种非常流行的做法 - 盲目地使用shared_ptr来做所有事情。

你应该清楚你的设计中哪些指针是所有者,哪些是观察者。

对于所有者使用shared_ptr

对于观察者来说,使用weak_ptr - 所有这些,而不仅仅是你认为可能属于周期的那些。

如果您遵循这种做法,那么循环引用不会引起任何问题,您无需担心它们。 当然,当您想要使用它们时,您将需要编写大量代码来将所有这些weak_ptrs转换为shared_ptrs - Boost确实无法胜任。

答案 2 :(得分:3)

检测周期相当容易:

  • 将计数设置为一些较大的数字,比如1000(具体大小取决于您的应用)
  • 从您感兴趣的pionter开始,并按照它的指示
  • 对于您关注的每个指针,递减计数
  • 如果计数在到达指针链末尾之前降至零,则表示您有一个周期
然而,它不是非常有用。并且通常不可能解决重新计数指针的循环问题 - 这就是为什么发明了替代垃圾收集方案,如发明清除。

答案 3 :(得分:2)

我没有找到比绘制大型UML图并寻找周期更好的方法。

要进行调试,我使用实例计数器进入注册表,如下所示:

template <DWORD id>
class CDbgInstCount
{
public:
#ifdef _DEBUG
   CDbgInstCount()   { reghelper.Add(id, 1); }
   CDbgInstCount(CDbgInstCount const &) {  reghelper.Add(id, 1); }
   ~CDbgInstCount()  { reghelper.Add(id, -1); }
#else
#endif
};

我只是想将它添加到相关的类中,并查看注册表。

(ID,如果以'XYZ!'的形式给出,将被转换为字符串。不幸的是,您不能将字符串常量指定为模板参数)

答案 4 :(得分:2)

boost::weak_ptrboost::shared_ptr的组合可能吗? This文章可能很有用。

答案 5 :(得分:1)

在图表中的detecting cycles上查看此帖子。

答案 6 :(得分:1)

找到循环的通用解决方案可以在这里找到:

Best algorithm to test if a linked list has a cycle

这假定您知道列表中对象的结构,并且可以跟踪每个对象中包含的所有指针。

答案 7 :(得分:1)

您可能需要使用垃圾收集器技术,例如Mark and Sweep。这个算法的想法是:

  1. 保留一个列表,其中包含对所有已分配内存块的引用。
  2. 在某些时候你启动垃圾收集器:
    1. 它首先标记它仍然可以访问的所有块,而不使用引用列表。
    2. 通过清单删除无法标记的每个项目,暗示它不再可用,因此无效。
  3. 由于您正在使用shared_ptr任何仍未存在的指针,因此您应该将其视为一个周期的成员。

    实施

    下面我将介绍一个如何实现算法的sweep()部分的非常简单的示例,但它将reset()收集器上的所有剩余指针。

    此代码存储shared_ptr<Cycle_t>个指针。类Collector负责跟踪所有指针并在执行sweep()时删除它们。

    #include <vector>
    #include <memory>
    
    class Cycle_t;
    typedef std::shared_ptr<Cycle_t> Ref_t;
    
    // struct Cycle;
    struct Cycle_t {
      Ref_t cycle;
    
      Cycle_t() {}
      Cycle_t(Ref_t cycle) : cycle(cycle) {}
    };
    
    struct collector {
      // Note this vector will grow endlessy.
      // You should find a way to reuse old links
      std::vector<std::weak_ptr<Cycle_t>> memory;
    
      // Allocate a shared pointer keeping
      // a weak ref on the memory vector:
      inline Ref_t add(Ref_t ref) {
        memory.emplace_back(ref);
        return ref;
      }
      inline Ref_t add(Cycle_t value) {
        Ref_t ref = std::make_shared<Cycle_t>(value);
        return add(ref);
      }
      inline Ref_t add() {
        Ref_t ref = std::make_shared<Cycle_t>();
        return add(ref);
      }
    
      void sweep() {
        // Run a sweep algorithm:
        for (auto& ref : memory) {
          // If the original shared_ptr still exists:
          if (auto ptr = ref.lock()) {
            // Reset each pointer contained within it:
            ptr->cycle.reset();
    
            // Doing this will trigger a deallocation cascade, since
            // the pointer it used to reference will now lose its
            // last reference and be deleted by the reference counting
            // system.
            //
            // The `ptr` pointer will not be deletd on the cascade
            // because we still have at least the current reference
            // to it.
          }
          // When we leave the loop `ptr` loses its last reference
          // and should be deleted.
        }
      }
    };
    

    然后您可以像这样使用它:

    Collector collector;
    
    int main() {
      // Build your shared pointers:
      {
        // Allocate them using the collector:
        Ref_t c1 = collector.add();
        Ref_t c2 = collector.add(c1);
    
        // Then create the cycle:
        c1.get()->cycle = c2;
    
        // A normal block with no cycles:
        Ref_t c3 = collector.add();
      }
    
      // In another scope:
      {
        // Note: if you run sweep an you still have an existing
        // reference to one of the pointers in the collector
        // you will lose it since it will be reset().
        collector.sweep();
      }
    }
    

    我用Valgrind进行了测试,没有内存泄漏或者#34;仍然可以访问&#34;列出了块,因此它可能正如预期的那样工作。

    关于此实施的一些注意事项:

    1. 内存向量会不断增长,你应该使用some memory allocation technique来确保它不会占用你所有的工作记忆。
    2. 有人可能会争辩说,不需要使用shared_ptr(类似于引用计数GC)来实现这样的垃圾收集器,因为Mark和Sweep算法已经完成了这项工作。
    3. 我没有实现mark()函数,因为它会使示例复杂化但是有可能,我将在下面解释。
    4. 最后,如果你关心(2),这种实现并不是闻所未闻。 CPython(Python的主要实现)确实使用了引用计数和Mark和Sweep的混合,但主要用于historical reasons

      实施mark()功能:

      要实现mark()功能,您需要进行一些修改:

      需要向bool marked;添加Cycle_t属性,并使用它来检查指针是否已标记。

      您需要编写如下所示的Collector::mark()函数:

      void mark(Ref_t root) {
        root->marked = true;
      
        // For each other Ref_t stored on root:
        for (Ref_t& item : root) {
          mark(item);
        }
      }
      

      然后你应该修改sweep()函数来删除标记,如果指针被标记,或者reset()指针:

      void sweep() {
        // Run a sweep algorithm:
        for (auto& ref : memory) {
          // If it still exists:
          if (auto ptr = ref.lock()) {
            // And is marked:
            if (ptr->marked) {
              ptr->marked = false;
            } else {
              ptr->cycle.reset();
            }
          }
        }
      }
      

      这是一个冗长的解释,但我希望它有所帮助。

答案 8 :(得分:0)

回答旧问题,您可以尝试使用侵入式指针,这可能有助于计算被引用资源的次数。

#include <cstdlib>
#include <iostream>

#include <boost/intrusive_ptr.hpp>

class some_resource
{
    size_t m_counter;

public:
    some_resource(void) :
        m_counter(0)
    {
        std::cout << "Resource created" << std::endl;
    }

    ~some_resource(void)
    {
        std::cout << "Resource destroyed" << std::endl;
    }

    size_t refcnt(void)
    {
        return m_counter;
    }

    void ref(void)
    {
        m_counter++;
    }

    void unref(void)
    {
        m_counter--;
    }
};

void
intrusive_ptr_add_ref(some_resource* r)
{
    r->ref();
    std::cout << "Resource referenced: " << r->refcnt()
              << std::endl;
}

void
intrusive_ptr_release(some_resource* r)
{
    r->unref();
    std::cout << "Resource unreferenced: " << r->refcnt()
              << std::endl;
    if (r->refcnt() == 0)
        delete r;
}

int main(void)
{
    boost::intrusive_ptr<some_resource> r(new some_resource);
    boost::intrusive_ptr<some_resource> r2(r);

    std::cout << "Program exiting" << std::endl;

    return EXIT_SUCCESS;
}

这是返回的结果。

Resource created 
Resource referenced: 1 
Resource referenced: 2 
Program exiting 
Resource unreferenced: 1
Resource unreferenced: 0 
Resource destroyed
*** Program Exit ***

答案 9 :(得分:-1)

我知道你说“没有weak_ptr”,但为什么不呢?让你的头部有一个weak_ptr到尾部,并且尾部有一个weak_ptr来阻止循环。