使用shared_ptr时如何检测循环

发布于 2024-07-17 07:27:40 字数 375 浏览 13 评论 0原文

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

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

请不要提出诸如“不要循环”或“使用 weak_ptr”之类的建议。

编辑

我不喜欢只使用weak_ptr的建议,因为显然如果你知道你将创建一个循环,那么你就不会有问题。 如果您在运行时生成 shared_ptr,您也不知道在编译时是否会出现循环。

所以请自行删除其中使用 weak_ptr 的答案,因为我特别要求不要有此类答案......

shared_ptr is a reference counting smart pointer in the Boost library.

The problem with reference counting is that it cannot dispose of cycles. I am wondering how one would go about solving this in C++.

Please no suggestions like: "don't make cycles", or "use weak_ptr".

Edit

I don't like suggestions that say to just use a weak_ptr because obviously if you know you will create a cycle, then you wouldn't have a problem. You also cannot know you will have a cycle at compile time if you generate shared_ptrs at runtime.

So please, self delete answers that use weak_ptr in them because I specifically asked not to have those kind of answers...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

篱下浅笙歌 2024-07-24 07:27:40

shared_ptr 表示所有权关系。 而weak_ptr代表意识。 多个对象相互拥有意味着您会遇到架构问题,这可以通过将一个或多个自己的更改为意识到的(即weak_ptr 的)。

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

shared_ptr represents ownership relation. While weak_ptr represents awareness. Having several objects owning each other means you have problems with architecture, which is solved by changing one or more own's into aware of's (that is, weak_ptr's).

I don't get why suggesting weak_ptr is considered useless.

桃气十足 2024-07-24 07:27:40

我理解您对被告知使用weak_ptr来破坏循环引用而感到烦恼,而当我被告知循环引用是糟糕的编程风格时,我几乎感到愤怒。

您具体询问如何发现循环引用。 事实是,在复杂的项目中,一些引用循环是间接的且难以发现。

答案是,您不应该做出虚假声明,以免容易受到循环引用的影响。 我是认真的,我正在批评一种非常流行的做法——盲目地使用shared_ptr来完成所有事情。

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

对于所有者,请使用 shared_ptr

对于观察者,请使用 weak_ptr - 所有观察者,而不仅仅是那些您认为可能是循环一部分的观察者。

如果您遵循这种做法,那么循环引用将不会导致任何问题,您无需担心它们。
当然,当您想使用这些 weak_ptr 时,您需要编写大量代码将它们转换为 shared_ptr - Boost 确实无法胜任这项工作。

I understand your annoyance at being glibly told to use weak_ptr to break cyclic references and myself I almost feel rage when I am told that cyclic references are bad programming style.

Your ask specifically how do you spot cyclic references. The truth is that in a complex project some reference cycles are indirect and difficult to spot.

The answer is that you should not make false declarations that leave you vulnerable to cyclic references. I am serious and I am criticizing a very popular practice - blindly using shared_ptr for everything.

You should be clear in your design which pointers are owners and which are observers.

For owners use shared_ptr.

For observers use weak_ptr - all of them, not just those you think may be part of a cycle.

If you follow this practice then the cyclic references will not cause any problems and you don't need to worry about them.
Of course you will have a lot of code to write to convert all these weak_ptrs to shared_ptrs when you want to use them - Boost really isn't up to the job.

凉月流沐 2024-07-24 07:27:40

检测循环相当容易:

  • 将计数设置为某个较大的数字,例如 1000(确切大小取决于您的应用程序)
  • 从您感兴趣的指针开始,并为您跟踪的每个指针跟踪来自它的指针
  • 减少,则递减计数
  • ,如果计数 在到达指针链末端之前下降到零,你就有了一个循环

,但是这并不是很有用。 而且通常不可能解决引用计数指针的循环问题 - 这就是为什么发明了替代垃圾收集方案(例如生成清除)的原因。

It's fairly easy to detect cycles:

  • set a count to some largish number, say 1000 (exact size depends on your application)
  • start with the pionter you are interested in and follow pointers from it
  • for each pointer you follow, decrement the count
  • if the count drops to zero before you reach the end of the pointer chain, you have a cycle

It's not, however, very useful. And it is not generally possible to solve the cycvle problem for ref-counted pointers - that's why alternative garbage colection schemes like generation scavenging were invented.

听不够的曲调 2024-07-24 07:27:40

我还没有找到比绘制大型 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!”,将被转换为字符串。不幸的是,您不能指定字符串常量作为模板参数)

I haven't found a much better method than drawing large UML graphs and looking out for cycles.

To debug, I use an instance counter going to the registry, like this:

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

I just ned to add that to the classes in question, and have a look at the registry.

(The ID, if given as e.g. 'XYZ!' will be converted to a string. Unfortunately, you can't specify a string constant as template parameter)

墨小沫ゞ 2024-07-24 07:27:40

也许是 boost::weak_ptrboost::shared_ptr 的组合? 这个文章可能感兴趣。

A combination of boost::weak_ptr and boost::shared_ptr maybe? This article may be of interest.

泛滥成性 2024-07-24 07:27:40

请参阅这篇关于检测周期的文章图形。

See this post on detecting cycles in a graph.

北斗星光 2024-07-24 07:27:40

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

测试链表是否有循环的最佳算法

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

The generic solution to finding a cycle can be found here:

Best algorithm to test if a linked list has a cycle

This assumes that you know the structure of the objects in the list, and can follow all of the pointers contained in each object.

雪化雨蝶 2024-07-24 07:27:40

您可能需要垃圾收集器技术,例如 标记和清除。 该算法的思想是:

  1. 保留一个列表,其中包含对所有分配的内存块的引用。
  2. 在某个时刻你启动垃圾收集器:
    1. 它首先标记所有在不使用参考列表的情况下仍可访问的块。
    2. 它会遍历列表,删除每个无法标记的项目,这意味着它不再可访问,因此没有用处。

由于您正在使用 shared_ptr ,因此您无法到达的任何仍然存在的指针都应被视为循环的成员。

实现

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

此代码存储 shared_ptr 指针。 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 测试了它,没有列出内存泄漏或“仍然可达”块,所以它可能按预期工作。

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

  1. 内存向量将无限增长,您应该使用一些内存分配技术 确保它不会占用您所有的工作记忆。
  2. 有人可能会争辩说,没有必要使用shared_ptr(其工作原理类似于引用计数 GC)来实现这样的垃圾收集器,因为标记和清除算法已经完成了这项工作。
  3. 我没有实现 mark() 函数,因为它会使示例复杂化,但这是可能的,我将在下面解释它。

最后,如果您关心(2),这种实现并非闻所未闻。 CPython(Python 的主要实现)确实混合使用了引用计数和标记和扫描,但主要用于 历史原因

实现 mark() 函数:

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

需要添加一个 bool 标记;< /code> 属性赋给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();
      }
    }
  }
}

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

You are probably in need of a Garbage Collector technique such as Mark and Sweep. The idea of this algorithm is:

  1. Keep a list with references to all memory blocks allocated.
  2. At some point you start the garbage collector:
    1. It first marks all the blocks it can still access without using the reference list.
    2. It goes through the list erasing each item that could not be marked, implying it is not reachable anymore so it is not useful.

Since you are using shared_ptr any still existing pointers you fail to reach should be considered as members of a cycle.

Implementation

Below I describe a very naive example of how to implement the sweep() part of the algorithm, but it will reset() all remaining pointers on the collector.

This code stores shared_ptr<Cycle_t> pointers. The class Collector is responsible for keeping track of all the pointers and deleting them when sweep() is executed.

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

You can then use it like this:

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

I tested it with Valgrind and no memory leaks or "still reachable" blocks were listed, so it is probably working as expected.

Some notes on this implementation:

  1. The memory vector will grow endlessly, you should use some memory allocation technique to make sure it does not occupy all your working memory.
  2. One may argue that there is no need of using shared_ptr (that works like a Reference Counting GC) to implement such a Garbage Collector since the Mark and Sweep algorithm would already take care of the job.
  3. I did not implement the mark() function because it would complicate the example but it is possible and I will explain it below.

Finally, if you are concerned with (2), this kind of implementation is not unheard of. CPython (the main implementation of Python) does use a mixture of Reference Counting and Mark and Sweep, but mostly for historical reasons.

Implementing the mark() function:

To implement the mark() function you will need to make some modifications:

It would be required to add a bool marked; attribute to Cycle_t, and use it to check whether the pointer is marked or not.

You will need to write the Collector::mark() function that would look like this:

void mark(Ref_t root) {
  root->marked = true;

  // For each other Ref_t stored on root:
  for (Ref_t& item : root) {
    mark(item);
  }
}

And then you should modify the sweep() function to remove the mark if the pointer is marked or else reset() the pointer:

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

It was a lengthy explanation, but I hope it helps someone.

感情旳空白 2024-07-24 07:27:40

如果你有带有共享指针的 Cycles,你将会遇到内存泄漏。 因此,如果转储内存泄漏对象,您可以查看这些对象的类型以找到实际的循环。

If you have Cycles with shared pointers, you will have memory leak. So, if you dump memory leak objects, you can look at the types of these to find the actual cycle.

牵强ㄟ 2024-07-24 07:27:40

我认为您确实需要 类似 Java 垃圾收集的东西这个问题讨论了<的“自动循环断路器”代码>shared_ptr。

可以在程序中使用shared_ptr循环并释放每个对象,但这违反了流行的建议。 流行的建议是打破shared_ptr循环在参与循环的对象之一中使用 weak_ptr

如果您坚持在程序中保留 shared_ptr 循环,您仍然可以这样做,但您必须在销毁时手动打破 shared_ptr 循环时间。

这很像记住在对象上手动调用delete,因此您可以明白为什么不建议这样做。

struct B;

struct A {
    shared_ptr<B> b;
    void prepForShutdown() {
        b = nullptr; // unlink from b.
    }
    ~A() { puts("~A"); }
};

struct B {
    shared_ptr<A> a;
    ~B() { puts("~B"); }
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->b = b;
    b->a = a;

    a->prepForShutdown();  // Break the cycle
    // Without this, either dtor cannot run, because A holds a reference 
    // to b and B holds a reference to A.

    a = nullptr;
    b = nullptr;

}

I think you're really asking for something like Java's garbage collection. This question talks about an "automatic cycle breaker" for shared_ptr.

You can have shared_ptr cycles in your program and have every object deallocate, but it's against the popular recommendation. The popular recommendation is to break the shared_ptr cycle by using weak_ptr in one of the objects that participates in the cycle.

If you insist on keeping a shared_ptr cycle in your program, you still can do it, but you have to manually break the shared_ptr cycle at destruction time.

This is a lot like remembering to manually call delete on an object, so you can see why it's not recommended.

struct B;

struct A {
    shared_ptr<B> b;
    void prepForShutdown() {
        b = nullptr; // unlink from b.
    }
    ~A() { puts("~A"); }
};

struct B {
    shared_ptr<A> a;
    ~B() { puts("~B"); }
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->b = b;
    b->a = a;

    a->prepForShutdown();  // Break the cycle
    // Without this, either dtor cannot run, because A holds a reference 
    // to b and B holds a reference to A.

    a = nullptr;
    b = nullptr;

}
落花浅忆 2024-07-24 07:27:40

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

#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 ***

Answer for the old question, you may try out the intrusive pointer which may help out to count how many times the resource being referred.

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

Here's the result returned.

Resource created 
Resource referenced: 1 
Resource referenced: 2 
Program exiting 
Resource unreferenced: 1
Resource unreferenced: 0 
Resource destroyed
*** Program Exit ***
仙气飘飘 2024-07-24 07:27:40

只使用 Valgrind 或 LLVM 的地址清理程序怎么样?

对于类似的内容:

#include <bits/stdc++.h>
using namespace std;

struct A {
shared_ptr<A> p;
    ~A() { cout << "Destructor called" << endl; }
};

int main() {
  shared_ptr<A> x(new A);
  shared_ptr<A> y(new A);
  x -> p = y;
  y -> p = x;
  return 0;
}

我看到:

==12156== Memcheck, a memory error detector
==12156== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==12156== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==12156== Command: ./a.out
==12156==
==12156==
==12156== HEAP SUMMARY:
==12156==     in use at exit: 80 bytes in 4 blocks
==12156==   total heap usage: 5 allocs, 1 frees, 72,784 bytes allocated
==12156==
==12156== 80 (16 direct, 64 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4
==12156==    at 0x4844F2F: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12156==    by 0x40121C: main (main.cpp:37)
==12156==
==12156== LEAK SUMMARY:
==12156==    definitely lost: 16 bytes in 1 blocks
==12156==    indirectly lost: 64 bytes in 3 blocks
==12156==      possibly lost: 0 bytes in 0 blocks
==12156==    still reachable: 0 bytes in 0 blocks
==12156==         suppressed: 0 bytes in 0 blocks
==12156==
==12156== For lists of detected and suppressed errors, rerun with: -s
==12156== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

definitely loss: 16 bytes in 1 block 检测到它。 您可以使用地址清理程序获得类似的输出。 我同意 Valgrind / ASan 也会检测其他内容,并且不会查明原因,但它会检测循环依赖。

How about just using Valgrind or LLVM's address santizer?

For something like:

#include <bits/stdc++.h>
using namespace std;

struct A {
shared_ptr<A> p;
    ~A() { cout << "Destructor called" << endl; }
};

int main() {
  shared_ptr<A> x(new A);
  shared_ptr<A> y(new A);
  x -> p = y;
  y -> p = x;
  return 0;
}

I see:

==12156== Memcheck, a memory error detector
==12156== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==12156== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info
==12156== Command: ./a.out
==12156==
==12156==
==12156== HEAP SUMMARY:
==12156==     in use at exit: 80 bytes in 4 blocks
==12156==   total heap usage: 5 allocs, 1 frees, 72,784 bytes allocated
==12156==
==12156== 80 (16 direct, 64 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4
==12156==    at 0x4844F2F: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12156==    by 0x40121C: main (main.cpp:37)
==12156==
==12156== LEAK SUMMARY:
==12156==    definitely lost: 16 bytes in 1 blocks
==12156==    indirectly lost: 64 bytes in 3 blocks
==12156==      possibly lost: 0 bytes in 0 blocks
==12156==    still reachable: 0 bytes in 0 blocks
==12156==         suppressed: 0 bytes in 0 blocks
==12156==
==12156== For lists of detected and suppressed errors, rerun with: -s
==12156== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

The definitely lost: 16 bytes in 1 blocks detects it. You can get a similar output with the address sanitizer. I agree that Valgrind / ASan will detect other stuff too and will not pin-point the cause, but it will detect the circular dependency.

余生共白头 2024-07-24 07:27:40

我知道你说“没有weak_ptr”,但为什么不呢? 让 head 带有一个weak_ptr 到tail,而tail 带有一个weak_ptr 到head 将阻止循环。

I know you said "no weak_ptr" but why not? Having your head with a weak_ptr to tail, and tail with a weak_ptr to head will prevent the cycle.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文