C++ shared_ptr<Base>指针访问冲突

C++ shared_ptr<Base> pointer acces violation

本文关键字:Base 指针 访问冲突 gt lt shared ptr C++      更新时间:2023-10-16

我使用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;"销毁"<<名字& lt; & lt;std:: endl;

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

我希望有人能告诉我我做错了什么!但更重要的是,我该如何补救!

看add()

中的这一行
i->parent = pBase(this);

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

尝试(作为开始)将parent设置为一个普通的哑指针。

Base *parent;

只是为了给其他人的答案添加一些:在行中做你想做的事情的规范方式

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()代替this当你想要一个std::shared_ptr。有问题的行变成

    i->parent = shared_from_this();
    

这里

i->parent = pBase(this);

你创建了一个智能指针从一个普通的旧指针到一个对象,你没有直接从new。千万不要这样做。

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

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

如果你想初始化一个智能指针,你有两个选择:在每个接口中使用智能指针。不幸的是,这对"this"不起作用,因为这是一个隐式参数。您需要将已经创建的智能指针以额外参数的形式手动传递给该方法。这样的:

tree->add(tree, child);

这有点难看,所以你可能想要考虑让"add"成为一个静态方法,这样你就不需要传递父类两次了。

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

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