我将shared_ptr<Base>
用于某种带有派生类的树列表。但是当我的树被破坏时,我得到了一个指针访问冲突。
我的代码看起来像这样,此外,这实际上模拟了我的运行时错误:
#include <iostream>
#include <memory>
#include <vector>
class Base;
typedef std::shared_ptr<Base> pBase;
class Derived;
class Base {
public:
std::vector<pBase> children;
pBase parent;
Base() {}
virtual ~Base() {}
virtual void doSomething() {}
void add(pBase i);
};
class Derived : public Base {
void doSomething() {
// Do something...
}
};
void Base::add(pBase i) {
i->parent = pBase(this);
children.push_back(i);
}
int main() {
pBase tree = pBase(new Derived());
pBase child(new Derived());
child->add(pBase(new Derived()));
tree->add(child);
}
当我向Base::~Base
添加以下行时:
std :: cout&lt;&lt; &#34;破坏&#34; &LT;&LT;名称&lt;&lt;的std :: ENDL;
在Base
中实现一个名为name的std :: string,对于每个实例都是不同的,我可以看到析构函数被多次调用(因为我认为Base::parent
引用)。那个问题引发了我的错误,但是我仍然不明白为什么会发生这种错误,因为shared_ptr<Base>
在实际销毁它之前需要计算它的引用!!?
我希望有人可以告诉我我做错了什么! 但更重要的是,我如何解决这个问题!
答案 0 :(得分:3)
在add()
中查看此行i->parent = pBase(this);
每次调用add时,您都会创建一个指向this
的 new 共享指针。这些共享指针是分开的 - 也就是说,它们是 NOT '共享',如你所想。因此,第一次删除子项时,它的父项被删除(因为它是一个共享指针)。因此你的代码就会爆炸。
尝试(作为开始)使父母成为一个普通的哑指针。
Base *parent;
答案 1 :(得分:3)
只是为了增加其他人的答案:在行中做你想做的事情的规范方式
i->parent = pBase(this);
是使用std::enable_shared_from_this
。你
从中获取Base
class Base : std::enable_shared_from_this<Base> {
确保每个Base
实例归std::shared_ptr
所有。在您的情况下,这是可以的,因为您在
pBase child(new Derived());
如果需要shared_from_this()
,请使用this
代替std::shared_ptr
。有问题的行将成为
i->parent = shared_from_this();
答案 2 :(得分:2)
下面
i->parent = pBase(this);
你创建一个智能指针,从一个普通的旧指针指向一个你没有直接从new获得的对象。永远不要这样做。
正如@Roddy解释的那样,你会得到单独的智能指针对象,带有单独的引用计数器。一个指针的两个引用计数器将不起作用。
在你的情况下,正如@Roddy所提议的那样,使父对象成为正常指针可能没问题。这样,您就不会遇到循环引用问题。确保在删除父项后永远不会访问父指针。如果你将所有孩子与父母一起删除没有问题(这会自动发生,除非你在其他地方存储更多智能指针)
如果要初始化智能指针,基本上有两种选择:在每个界面中使用智能指针。不幸的是,这对“this”不起作用,因为这是一个隐含的参数。您需要在一个额外的参数中手动将已创建的智能指针传递给方法。像这样:
tree->add(tree, child);
这有点难看,所以你可能想考虑“添加”一个静态方法,所以你不需要传递父母两次。
另一种选择:使用另一种智能指针,如boost :: intrusive_ptr,您可以在其中存储引用计数。通过这种方式,您可以找到引用计数,即使您只有像“this”这样的哑指针。
编辑:以下@jpalecek的答案更好。使用那个。塞巴斯蒂安。