如何避免 C++ 中深层嵌套数据结构的析构函数堆栈溢出?

发布于 2024-12-20 12:51:32 字数 908 浏览 2 评论 0 原文

int count;

class MyClass {
    std::shared_ptr<void> p;
public:
    MyClass(std::shared_ptr<void> f):p(f){
        ++count;
    }
    ~MyClass(){
        --count;
    }
};

void test(int n){
    std::shared_ptr<void> p;
    for(int i=0;i<n;++i){
        p = std::make_shared<MyClass>(p);
    }
    std::cout<<count<<std::endl;
}

int main(int argc, char* argv[])
{
    test(200000);
    std::cout<<count<<std::endl;
    return 0;
}

上述程序会在 Visual Studio 2010 IDE 中的“release”版本下导致堆栈溢出。

问题是:如果您确实需要创建一些像上面这样的数据结构,如何避免这个问题。

更新:现在我看到了一个有意义的答案。然而这还不够好。请考虑我已经更新了 MyClass 以包含两个(或更多)shared_ptr,并且每个都可以是 MyClass 或其他一些实例数据。

更新:有人为我更新了标题并说“深度引用计数数据结构”,这与这个问题没有必要相关。实际上,shared_ptr只是一个方便的例子;您可以轻松地更改为具有相同问题的其他数据类型。我还删除了 C++11 标签,因为它也不是 C++11 唯一的问题。

int count;

class MyClass {
    std::shared_ptr<void> p;
public:
    MyClass(std::shared_ptr<void> f):p(f){
        ++count;
    }
    ~MyClass(){
        --count;
    }
};

void test(int n){
    std::shared_ptr<void> p;
    for(int i=0;i<n;++i){
        p = std::make_shared<MyClass>(p);
    }
    std::cout<<count<<std::endl;
}

int main(int argc, char* argv[])
{
    test(200000);
    std::cout<<count<<std::endl;
    return 0;
}

The above program causes stack overflow under "release" build in Visual Studio 2010 IDE.

The question is: if you do need to create some data structure like the above, how to avoid this problem.

UPDATE: Now I have seen one meaningful answer. However this is not good enough. Please consider I have updated MyClass to contain two (or more) shared_ptrs, and each of them can be an instance of MyClass or some other data.

UPDATE: Somebody updated the title for me and saying "deep ref-counted data structure", which is not necessary related to this question. Actually, shared_ptr is only a convenient example; you can easily change to other data types with the same problem. I also removed the C++11 tag because it is not C++11 only problem as well.

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

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

发布评论

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

评论(3

染火枫林 2024-12-27 12:51:33
  • 使堆栈显式化(即将其放入堆上的容器中)。
  • 拥有非不透明指针(非空),以便您可以遍历您的结构。
  • 取消将深度递归结构嵌套到堆容器上,使该结构成为非递归结构(通过在进行过程中断开它的连接)。
  • 通过迭代上面收集的指针来释放所有内容。

像这样,改变 p 的类型,以便我们可以检查它。

std::shared_ptr<MyClass> p;

~MyClass() {
    std::stack<std::shared_ptr<MyClass>> ptrs;
    std::shared_ptr<MyClass> current = p;

    while(current) {
        ptrs.push_back(current);
        current = current->p;
        ptrs.back()->p.reset(); // does not call the dtor, since we have a copy in current
    }

    --count;
    // ptrs dtor deallocates every ptr here, and there's no recursion since the objects p member is null, and each object is destroyed by an iterative for-loop
}

最后一些提示:

  • 如果您想理清任何结构,您的类型应该提供一个返回并释放所有内部shared_ptr的接口,即类似: std::vector>如果您不能将自己限制在 MyClass 中,则可能在 ISharedContainer 接口或其他内容中。
  • 对于递归结构,您应该检查是否将相同的对象添加到 ptr 容器中两次。
  • Make the stack explicit (i.e. put it in a container on the heap).
  • Have non-opaque pointers (non-void) so that you can walk your structure.
  • Un-nest your deep recursive structure onto the heap container, making the structure non-recursive (by disconnecting it as you go along).
  • Deallocate everything by iterating over the pointers collected above.

Something like this, with the type of p changed so we can inspect it.

std::shared_ptr<MyClass> p;

~MyClass() {
    std::stack<std::shared_ptr<MyClass>> ptrs;
    std::shared_ptr<MyClass> current = p;

    while(current) {
        ptrs.push_back(current);
        current = current->p;
        ptrs.back()->p.reset(); // does not call the dtor, since we have a copy in current
    }

    --count;
    // ptrs dtor deallocates every ptr here, and there's no recursion since the objects p member is null, and each object is destroyed by an iterative for-loop
}

Some final tips:

  • If you want to untangle any structure, your types should provide an interface that returns and releases all internal shared_ptr's, i.e something like: std::vector<shared_ptr<MyClass>> yieldSharedPtrs(), perhaps within a ISharedContainer interface or something if you can't restrict yourself to MyClass.
  • For recursive structures, you should check that you don't add the same object to your ptr-container twice.
灵芸 2024-12-27 12:51:33

感谢@Macke的提示,我有一个改进的解决方案,如下所示:

~MyClass(){
   DEFINE_THREAD_LOCAL(std::queue< std::shared<void> >, q)
   bool reentrant = !q.empty();
   q.emplace(std::move(p)); //IMPORTANT!
   if(reentrant) return;

   while(!q.empty()){
       auto pv = q.front();
       q.pop();
   }
}

DEFINE_THREAD_LOCAL是一个宏,它将变量(参数2)定义为具有线程本地存储类型的指定类型(参数1),这意味着有每个正在运行的线程不超过一个实例。由于 thread_local 关键字对于主流编译器仍然不可用,因此我必须假设这样一个宏才能使其适用于编译器。

对于单线程程序,DEFINE_THREAD_LOCAL(type, var) 很简单。

static type var;

此解决方案的好处是它不需要更改类定义。

与@Macke的解决方案不同,我使用 std::queue 而不是 std::stack 来保持销毁顺序。

在给定的测试用例中,q.size() 永远不会超过 1。然而,这只是因为该算法是广度优先的。如果 MyClass 有更多到 MyClass 另一个实例的链接,q.size() 将达到更大的值。

注意:重要的是要记住使用 std::move 将 p 传递到队列。如果你忘记这样做,你就没有解决问题,你只是创建和销毁 p 的新副本,并且在可见代码之后,销毁仍然是递归的。

更新:原始发布的代码有一个问题:q 将在 pop() 调用中进行修改。解决方案是缓存q.front()的值以供以后销毁。

Thanks to @Macke's tips, I have an improved solution like the following:

~MyClass(){
   DEFINE_THREAD_LOCAL(std::queue< std::shared<void> >, q)
   bool reentrant = !q.empty();
   q.emplace(std::move(p)); //IMPORTANT!
   if(reentrant) return;

   while(!q.empty()){
       auto pv = q.front();
       q.pop();
   }
}

DEFINE_THREAD_LOCAL is a macro that defines a variable (param 2) as specified type (param 1) with thread local storage type, which means there is no more than one instance for each running thread. Because thread_local keyword is still not available for mainstream compilers, I have to assume such a macro to make it work for compilers.

For single thread programs, DEFINE_THREAD_LOCAL(type, var) is simply

static type var;

The benefit of this solution is it do not require to change the class definition.

Unlike @Macke's solution, I use std::queue rather than std::stack in order to keep the destruction order.

In the given test case, q.size() will never be more than 1. However, it is just because this algorithm is breadth-first. If MyClass has more links to another instance of MyClass, q.size() will reach greater values.

NOTE: It is important to remember use std::move to pass p to the queue. You have not solved the problem if you forgotten to do so, you are just creating and destroying a new copy of p, and after the visible code the destruction will still be recursive.

UPDATE: the original posted code has a problem: q is going to be modified within pop() call. The solution is cache the value of q.front() for later destruction.

荭秂 2024-12-27 12:51:33

如果您确实必须使用如此奇怪的代码,则可以增加堆栈的大小。您应该在 Visual Studio 的项目属性中找到此选项。

正如已经建议的,我必须告诉您,在处理大量数据结构时应该避免这种代码,并且如果您计划发布软件,增加堆栈大小并不是一个好的解决方案。显然,如果您滥用此功能,它也可能会严重降低您自己的计算机的速度。

If you really have to work with such an odd code, you can increase the size of your stack. You should find this option in the project properties of Visual Studio.

As already suggested, I must tell you that this kind of code should be avoided when working with a large mole of data structures, and increasing the stack size is not a good solution if you plan to release your software. It may also terribly slow down your own computer if you abuse this feature, obviously.

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