0

我正在使用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 << "destruct" << name << std::endl;

并实现一个名为 name 的 std::string ,Base其中每个实例都不同,我可以看到析构函数被多次调用(因为Base::parent我认为是引用)。那当然触发了我的错误,但我仍然不明白它为什么会发生,因为shared_ptr<Base>预计会在实际销毁它之前计算它的引用!!?

我希望有人能告诉我我做错了什么!但更重要的是,我该如何解决这个问题!

4

3 回答 3

3

在 add() 中查看这一行

i->parent = pBase(this);

每次调用 add 时,都会创建一个的指向this. 这些共享指针是分开的——也就是说,它们不像你想象的那样“共享”。所以,当你第一次删除一个孩子时,它的父母会被删除(因为它是一个共享指针)。因此,您的代码爆炸了。

尝试(作为开始)使 parent 成为一个普通的哑指针。

Base *parent;
于 2013-01-16T19:23:40.373 回答
3

只是添加到其他人的答案:在行中做你想做的事情的规范方法

i->parent = pBase(this);

是使用std::enable_shared_from_this. 你

  1. Base从中衍生

    class Base : std::enable_shared_from_this<Base> {
    
  2. 确保每个Base实例都归std::shared_ptr. 在您的情况下没关系,因为您在表达式中创建对象,例如

    pBase child(new Derived());
    
  3. 当你想要shared_from_this()一个. 然后有问题的行将变为thisstd::shared_ptr

    i->parent = shared_from_this();
    
于 2013-01-16T19:49:05.870 回答
2

这里

i->parent = pBase(this);

您从一个普通的旧指针创建一个智能指针,该指针指向您没有直接从 new 获得的对象。永远不要这样做。

正如@Roddy 解释的那样,您会获得带有单独引用计数器的单独智能指针对象。一个指针的两个引用计数器将不起作用。

在您的情况下,正如@Roddy 建议的那样,将 parent 设为普通指针可能是可以的。这样,您就不会遇到循环引用的麻烦。只要确保您在删除父级后永远不会访问父级指针。如果您将所有子项与父项一起删除,则没有问题(这会自动发生,除非您在其他地方存储更多指向它们的智能指针)

如果要初始化智能指针,基本上有两种选择: 在每个接口中使用智能指针。不幸的是,这不适用于“this”,因为这是一个隐式参数。您需要在一个额外的参数中手动将您已经创建的智能指针传递给该方法。像这样:

tree->add(tree, child);

这有点难看,所以您可能需要考虑将“添加”作为静态方法,这样您就不需要两次传递父级。

另一种选择:使用另一种智能指针,如 boost::intrusive_ptr,您可以将引用计数存储在指针中。这样,即使您只有一个像“this”这样的哑指针,您也可以找到引用计数。

编辑:下面@jpalecek 的答案更好。使用那个。塞巴斯蒂安。

于 2013-01-16T19:23:39.920 回答