[英]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唯一的问题。
像这样,改变了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
}
最后一些提示:
std::vector<shared_ptr<MyClass>> yieldSharedPtrs()
,可能在ISharedContainer接口内,或者如果可以的话不要局限于MyClass。 如果确实需要使用这样的奇数代码,则可以增加堆栈的大小。 您应该在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.