继承和智能指针 (std::shared_ptr)

Inheritance and smart pointers (std::shared_ptr)

有很多话要说。首先,我想知道下面的方法是否被认为是一种设计模式,甚至是一种常用技术(这就是为什么我没有提供有关标题的更多信息)。如果是这样的话,名字是什么? 无论如何,这是我想要实现的目标的缩小版本。由于我需要使用复制,我发现使用 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(this) {}

this 传递给 shared_ptr 时要小心。

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

  1. 对于new Bar,会构造一个Bar类型的对象,并在其构造函数内部this传递给父class成员ptr,则该对象由 std::shared_ptr.

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

所以有两个 std::shared_ptr 指向同一个对象,但是 std::shared_ptr 对此一无所知;因为你正在分别构建它们。当 ff.ptr 被销毁时, pointed-to 对象也会被销毁。然后成员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 类型的成员指向派生的 class 是令人困惑的;分开可能更好。然后,我认为可能是 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();
    }
}

Foo::ptr 持有一个指向其母亲 Foo(this) 且引用计数为 1 的指针。

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

Foo死亡时,它的children也被杀死。所以 Foo::ptr 肯定也死了。然后将 new Bar 分配给 dead Foo::ptr 会导致 UB.