你可以使用删除器的成员来保持 shared_ptr 存活吗?

Can you use a member of deleter to keep alive a shared_ptr?

给定以下类型

// interface and implementation used in one part of the codebase
struct Image
{
    virtual std::vector<uint8_t>& GetData () = 0;
};

struct VecImage : public Image
{
    std::vector<uint8_t> mData;

    std::vector<uint8_t>& GetData () { return mData; }
};

// used in another part of the codebase
struct PtrImage
{
    std::shared_ptr<uint8_t> mData;

    PtrImage (std::shared_ptr<Image> pIm);
};

以下构造函数是否是将 Image 转换为 PtrImage 的明智且正确的方法?

PtrImage::PtrImage (std::shared_ptr<Image> pIm)
{
    struct im_deleter
    {
        std::shared_ptr<Image> keepAlive;
        void operator () (uint8_t* ptr)
        {
            keepAlive.reset ();
        }
    };

    mData = { &pIm->GetData()[0], im_deleter { pIm } };
}

PtrImage 用作 "value type",它按值传递,而 Image 仅在 shared_ptrs 中传递。

我觉得很危险:

std::shared_ptr<Image> i = std::make_shared<VecImage>(/* some data */);
PtrImage p(i); // has now stored a pointer to the vector's data
i->getData()->push_back(0); // repeat until re-allocation occurs!

p 现在会持有什么?共享指针持有指向re-allocation之前驻留在向量中的数据的指针;但此数据已被替换并被删除。所以你现在有一个悬空指针存储在 p 中(在 uint8_t 指针中),使用它(最迟在你的智能指针试图删除它的数据时发生)将导致未定义的行为。

is the following constructor a sane..

由于析构函数,您延长了 Image 的生命周期,因此 data 仍然有效。 所以你在这一点上是正确的...

但是,向量可能会重新分配,使缓冲区无效。

所以生成的代码是不安全的。

为了安全起见,您可以存储 std::shared_ptr<std::vector<uint8_t>> mData;

.. and correct way

我们有 better/simpler 别名构造函数 of std::shared_ptr:

struct PtrImage
{
    std::shared_ptr<std::vector<uint8_t>> mData;

    PtrImage (std::shared_ptr<Image> pIm) : mData(pIm, &pIm->GetData()) {}
};

因此所有权信息 PtrImage::mDatapIm 共享。

注意:我假设 GetData() 返回的向量具有与 Image 相同(或更长)的生命周期(对于 VecImage)。如果它是一个不相关的向量(来自其他对象),那么你将没有解决方案。

如评论中所述,vector 不应重新分配两者

你甚至不应该尝试让共享指针猜测它是否应该删除它的对象。如果你继续这样做,你会一次被一个角落案例抓住,或者即使你设法找到所有,一个明显无关的变化创建的新案例。

如果您需要将 Image 转换为 PtrImage,只需说明您想从 T 构建 shared_ptr<T>。有两种标准方法:复制或移动,并且 shared_ptr 拥有其对象的所有权。

在你的例子中,由于Image只有returns一个左值引用,你只能使用一个副本。您可以通过取得其 data 成员的所有权从 VecImage 移动。