【问题标题】:Inheritance and smart pointers (std::shared_ptr)继承和智能指针 (std::shared_ptr)
【发布时间】:2017-01-23 02:44:38
【问题描述】:

有很多话要说。首先,我想知道下面的方法是否被认为是一种设计模式甚至是一种通用技术(这就是为什么我没有提供有关标题的更多信息)。如果是这样,那叫什么名字? 无论如何,这是我想要实现的缩小版本。由于我需要使用复制,我发现使用 std::shared_ptr 是避免释放(删除)指针的最佳方式。

class Foo
{
public:
    Foo() : ptr(nullptr) {}
    Foo(const Foo& foo) : ptr(foo.ptr) {}
    virtual ~Foo() = default;

    void whatever() {
        if (ptr)
            ptr->whateverHandler();
    }

    void reset() {
        ptr.reset();
    }

    void resetBar() {
        ptr.reset(new Bar);
    }

    // Other resets here...

protected:
    Foo(Foo* foo) : ptr(foo) {}

private:
    // Every child class should override this
    virtual void whateverHandler() {
        throw "whateverHandler cant be called within base class";
    }

protected:
    std::shared_ptr<Foo> ptr;
};

class Bar : public Foo
{
public:
    Bar() : Foo(this) {}
    void whateverHandler() {
        printf("Bar's handler!!! \n");
    }
};

这一切看起来都很好并且编译得很好,但是,下面的考试崩溃了。这是为什么呢?

int main()
{
    {
        Foo f;

        f.resetBar();
    }

    return getchar();
}

【问题讨论】:

  • 当一个Bar被销毁时,它的Foo被销毁两次
  • 您可能需要std::enable_shared_from_this。但是,在这个特定的示例中,您也不需要它
  • 您还缺少虚拟析构函数。
  • @Danh 这正是发生的事情!为什么会这样?!

标签: c++ inheritance shared-ptr smart-pointers virtual-functions


【解决方案1】:
Bar() : Foo(this) {}

this 传递给shared_ptr 时要小心。

想想f.resetBar();ptr.reset(new Bar); 之后会发生什么。

  1. 对于new Bar,将构造一个Bar类型的对象,并在其构造函数内部将this传递给父类成员ptr,然后该对象由它管理std::shared_ptr

  2. 之后,对象由f.ptr管理;这是另一个std::shared_ptr

所以有两个std::shared_ptrs 指向同一个对象,但std::shared_ptrs 对此一无所知;因为您正在分别构建它们。当ff.ptr 被销毁时,指向的对象也将被销毁。然后成员ptr会被销毁,它会再次尝试销毁同一个对象,导致UB。

我不确定设计试图完成什么,但只是停止将 this 传递给 std::shared_ptr 就可以消除 UB。

class Foo
{
public:
    virtual ~Foo() = default;
    void whatever() {
        if (ptr)
            ptr->whateverHandler();
    }
    void reset() {
        ptr.reset();
    }
    void resetBar() {
        ptr.reset(new Bar);
    }
    // Other resets here...
private:
    // Every child class should override this
    virtual void whateverHandler() = 0;
    std::shared_ptr<Foo> ptr;
};

class Bar : public Foo
{
public:
    void whateverHandler() {
        printf("Bar's handler!!! \n");
    }
};

int main()
{
    {
        Foo f;
        f.resetBar();
        f.whatever();
        f.resetSthElse();
        f.whatever();
    }
}

而且 IMO,有一个类型为 std::shared_ptr 的成员指向派生类是令人困惑的;分开可能会更好。然后,我想可能是bridge design partern

class Foo
{
public:
    void whatever() {
        if (ptr)
            ptr->whateverHandler();
    }
    void reset() {
        ptr.reset();
    }
    void resetBar() {
        ptr.reset(new BarHandler);
    }
    // Other resets here...
private:
    std::shared_ptr<FooHandler> ptr;
};

class FooHandler
{
public:
    virtual ~FooHandler() = default;
    // Every child class should override this
    virtual void whateverHandler() = 0;
};

class BarHandler : public FooHandler
{
public:
    void whateverHandler() {
        printf("Bar's handler!!! \n");
    }
};

int main()
{
    {
        Foo f;
        f.resetBar();
        f.whatever();
        f.resetSthElse();
        f.whatever();
    }
}

【讨论】:

  • 我已经稍微改变了我的帖子以匹配真正的问题(旧的有点错误,你回答了它)。为什么d.resetFree() 会崩溃?
  • @YvesHenri 没有任何改变。对于f.resetBar();ptr.reset(new Bar);f.ptr 将指向构造的对象,该对象也由它自己的父成员ptr 指向。注意有两个FooBar类型的对象,它们的成员ptr指向同一个对象。
  • 那我怎样才能实现我想要的呢?我选择智能指针的原因是因为我需要在副本上传递基址的内部指针,但是我需要安全地检测它是否被删除。只需将其更改为原始指针并进行释放,您将拥有旧版本(我尝试过)。为了清楚起见,假设您声明了 2 个 Bars,但其中一个是通过复制构造函数(检索内部指针)创建的,但随后您销毁了复制的 Bar(例如超出范围)。现在副本将在下一次whatever 调用时崩溃! :(
  • @YvesHenri 我不确定这个设计想要实现什么;至少,停止传递this 将摆脱错误。 ptr.reset(new Bar); 表示构造的Bar 已经被一个std::shared_ptr 管理,不需要再添加一个。
  • 非常感谢您的帮助!这里的想法是使用whatever 在基类或其任何子类(继承)中,主要由基类。基类应该能够通过重置函数在它的一个孩子中“转换”自己,并且对于孩子来说也是如此(基础到它的任何孩子和任何孩子到另一个孩子)。通过子类调用不同的whatever 逻辑通过虚函数很容易,但是如果它是基类调用呢?我说清楚了吗?希望你已经明白了!我真的很感激帮助!顺便说一句,您的回答也被接受了。
【解决方案2】:

Foo::ptr 持有指向其母体Foo(this) 的指针,引用计数为 1。

Foo::resetBar(),当Foo要求Foo::ptr调用reset(new Bar)时,Foo::ptr放弃了它的所有权发给它的母亲Foo(this),发现引用计数已经减少到0,所以它需要杀死Foo

Foo 死了,它的孩子也被杀了。所以 Foo::ptr 也一定是死的。然后将new Bar 分配给死Foo::ptr 会导致UB。

【讨论】:

    猜你喜欢
    • 2020-07-10
    • 1970-01-01
    • 2014-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-10
    • 1970-01-01
    • 2012-09-07
    相关资源
    最近更新 更多