C++ 使用共享ptr时如何检测周期

C++ 使用共享ptr时如何检测周期,c++,garbage-collection,shared-ptr,reference-counting,C++,Garbage Collection,Shared Ptr,Reference Counting,shared_ptr是Boost库中的一个引用计数智能指针 引用计数的问题在于它不能处理循环。我想知道如何在C++中解决这个问题。p> 请不要提出诸如“不要循环”或“使用弱ptr”之类的建议 编辑 我不喜欢那些说只使用弱ptr的建议,因为很明显,如果你知道你将创建一个循环,那么你就不会有问题。如果在运行时生成共享的ptr,您也无法知道编译时会有一个周期 因此,请自行删除使用弱ptr的答案,因为我特别要求不要有此类答案…检测周期相当容易: 将计数设置为更大的数字,例如1000(具体大小取决于您的

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

引用计数的问题在于它不能处理循环。我想知道如何在C++中解决这个问题。p> 请不要提出诸如“不要循环”或“使用弱ptr”之类的建议

编辑

我不喜欢那些说只使用弱ptr的建议,因为很明显,如果你知道你将创建一个循环,那么你就不会有问题。如果在运行时生成共享的ptr,您也无法知道编译时会有一个周期


因此,请自行删除使用弱ptr的答案,因为我特别要求不要有此类答案…

检测周期相当容易:

  • 将计数设置为更大的数字,例如1000(具体大小取决于您的应用程序)
  • 从您感兴趣的pionter开始,并遵循它的指示
  • 对于跟随的每个指针,减少计数
  • 如果在到达指针链末端之前计数下降到零,则有一个循环

然而,它不是很有用。通常不可能解决ref计数指针的cycvle问题——这就是为什么发明了替代垃圾收集方案,如生成清理。

我没有找到比绘制大型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
};
模板
类CDbgInstCount
{
公众:
#ifdef_调试
CDbgInstCount(){reghelper.Add(id,1);}
CDbgInstCount(CDbgInstCount常量&){reghelper.Add(id,1);}
~CDbgInstCount(){reghelper.Add(id,-1);}
#否则
#恩迪夫
};
我只是想把它添加到相关的类中,并查看一下注册表


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

可能是
boost::weak_ptr
boost::shared_ptr
的组合?文章可能会引起兴趣。

共享\u ptr
表示所有权关系。而
弱\u ptr
代表意识。多个对象相互拥有意味着您在体系结构方面存在问题,这可以通过将一个或多个own对象更改为aware-of对象(即,
weak_-ptr
)来解决


我不明白为什么建议
弱ptr
被认为是无用的。

我知道你说过“不弱ptr”,但为什么不呢?让你的头和尾巴之间的距离很小,而尾巴和头部之间的距离也很小,这会阻止这种循环。

请看这篇文章的图表

找到循环的一般解决方案可以在这里找到:


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

我理解您被轻率地告知使用
弱\u ptr
来破坏循环引用时的烦恼,而我自己当被告知循环引用是糟糕的编程风格时,几乎感到愤怒

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

答案是,您不应该做出错误的声明,从而使您容易受到循环引用的攻击。我是认真的,我在批评一种非常流行的做法——盲目地将共享ptr用于所有事情

在设计中,您应该清楚哪些指针是所有者,哪些是观察者

对于所有者,请使用
共享\u ptr

对于观察者,请使用
弱\u ptr
-所有这些,而不仅仅是您认为可能属于某个周期的部分

如果您遵循这种做法,那么循环引用将不会导致任何问题,您不必担心它们。
当然,当你想使用它们时,你需要编写大量代码来将所有这些
弱ptr
s转换为
共享ptr
s,Boost真的不能胜任这项工作。

回答老问题,你可以尝试使用侵入式指针,它可以帮助你计算资源被引用的次数

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

您可能需要一种垃圾收集器技术,例如。该算法的思想是:

  • 保留一个引用所有已分配内存块的列表
  • 在某个时间点启动垃圾收集器:
  • 它首先标记在不使用参考列表的情况下仍可以访问的所有块
  • 它会在列表中删除每个无法标记的项,这意味着它不再是可访问的,因此它没有用处
  • 由于您使用的是
    shared_ptr
    ,因此您无法到达的任何仍然存在的指针都应被视为周期的成员

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

    此代码存储
    共享\u ptr
    指针。类
    收集器
    负责跟踪所有指针,并在执行
    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.
        }
      }
    };
    
    我用Valgrind对它进行了测试,没有列出内存泄漏或“仍然可以访问”的块,所以它可能按预期工作

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

  • 内存向量将无限增长,您应该使用它来确保它不会占用您所有的工作内存
  • 有人可能会争辩说,没有必要使用
    共享\u ptr
    (其工作原理类似于引用计数GC)来实现这样一个垃圾收集器,因为标记和扫描算法已经处理了这个任务
  • 我没有实现mark()函数,因为它会使示例复杂化,但这是可能的,我将在下面解释它
  • 最后,如果您关心(2),这种实现并非前所未闻。CPython(Python的主要实现)
    #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();
      }
    }
    
    void mark(Ref_t root) {
      root->marked = true;
    
      // For each other Ref_t stored on root:
      for (Ref_t& item : root) {
        mark(item);
      }
    }
    
    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();
          }
        }
      }
    }