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的问题。
答案 0 :(得分:2)
像这样的东西,改变了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的。答案 1 :(得分:0)
如果你真的需要处理这么奇怪的代码,你可以增加堆栈的大小。您应该在Visual Studio的项目属性中找到此选项。
正如已经建议的那样,我必须告诉你,在处理大量数据结构时应避免使用这种代码,如果计划发布软件,增加堆栈大小并不是一个好的解决方案。如果您滥用此功能,它可能也会使您自己的计算机速度变慢。
答案 2 :(得分:0)
感谢@ 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
是一个宏,它将变量(param 2)定义为具有线程本地存储类型的指定类型(param 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()
的值以便以后销毁。