繁体   English   中英

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

[英]How do I avoid destructor stack overflow with deep nested data structure in C++?

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中的“发行版”下导致堆栈溢出。

问题是:如果您确实需要创建类似上述的数据结构,如何避免此问题。

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

更新:有人为我更新了标题,并说“深度引用计数的数据结构”,与该问题无关。 实际上, shared_ptr只是一个方便的示例。 您可以轻松地将其他数据类型更改为同样的问题。 我也删除了C++11标记,因为它也不是C ++ 11唯一的问题。

  • 使堆栈显式(即,将其放入堆中的容器中)。
  • 具有非透明指针(非void),以便您可以遍历结构。
  • 将深层递归结构取消嵌套到堆容器中,使该结构成为非递归结构(通过在进行过程中断开连接)。
  • 通过遍历上面收集的指针来释放所有内容。

像这样,改变了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<shared_ptr<MyClass>> yieldSharedPtrs() ,可能在ISharedContainer接口内,或者如果可以的话不要局限于MyClass。
  • 对于递归结构,应检查是否没有两次将同一对象添加到ptr-container中。

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

正如已经建议的那样,我必须告诉您,在使用大量数据结构时应避免使用此类代码,如果计划发布软件,则增加堆栈大小不是一个好的解决方案。 显然,如果您滥用此功能,它也可能会极大降低计算机的速度。

感谢@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),这意味着每个正在运行的线程DEFINE_THREAD_LOCAL有一个实例。 因为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()的值以供以后销毁。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM