【问题标题】:Can you use a member of deleter to keep alive a shared_ptr?你可以使用 deleter 的成员来保持 shared_ptr 的存活吗?
【发布时间】:2019-10-03 11:20:34
【问题描述】:

给定以下类型

// 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 用作“值类型”,它是按值传递的,而Image 仅在 shared_ptrs 中传递。

【问题讨论】:

  • 随着答案中 cmets 的发展,我越来越觉得我们正在讨论 XY problem。你实际上想用这个结构解决什么问题?
  • 代码库周围有两种“图像”类型,我试图弄清楚是否可以在没有副本的情况下安全地将一种转换为另一种。 (而不是重写大量代码以在任何地方只使用一种图像类型。)
  • 您如何表示系统中的实际图像数据?我的意思是:如果你有e。 G。 JPEG数据,你会解码吗?如果您有一个通用的内部图像表示,那么我宁愿为每种图像类型编写一个加载器和一个存储类,每个都会加载数据并将其转换为内部表示(例如,每个像素设置一个 RGB 或 RGBA),而您finally 只会在内部处理一种图像类型。商店只会做相反的事情。然后图像转换可能如下所示:LoaderJPG l; Image i = l.load("path to file"); StorerPNG s; s.store(i, "path to file");
  • 如果您想将图像绘制到屏幕上或想要转换,无论如何您都需要所有图像类型的共同点......当然,上面的示例也可以使用智能指针完成,如果那更合适;我个人更愿意使用图像类inside 的智能指针,并将图像本身作为(const)引用或在某些特定情况下按值传递。如果您选择智能指针:让加载程序返回 std::unique_ptr - 将唯一指针移动到共享指针比在不需要时共享开销更有效。

标签: c++ shared-ptr value-type


【解决方案1】:

下面的构造函数是不是一个理智的..

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

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

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

为了安全起见,您可以存储std::shared_ptr&lt;std::vector&lt;uint8_t&gt;&gt; mData;

..正确的方法

我们有更好/更简单的 aliasing constructor 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 也不应该重新分配

【讨论】:

  • 来自cppreference: '然而,在这个shared_ptr上调用get()总是会返回一个ptr的副本。只要这个 shared_ptr 存在,程序员就有责任确保这个 ptr 保持有效'——如果我没看错的话,只有在向量中没有发生重新分配时我们才是安全的?
  • 问题中的实现也应该遇到同样的问题,不是吗?
  • @Aconcagua:确实,存储向量是安全的。答案固定处理这一点。
  • 虽然不完全是:在多线程场景中,当另一个线程通过某种方式(例如,通过删除元素或引发重新分配)使迭代器无效时,一个线程可能正在迭代向量。如果打算解决这些问题,那么,仍然失败......
  • 我知道。但是 OP 也可能已经意识到,并试图以这种方式保存原始数据。为什么要不然这样的结构?
【解决方案2】:

对我来说看起来很危险:

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 现在会持有什么?共享指针持有一个指针,指向在重新分配之前驻留在向量中的数据;但是此数据已被替换并被删除。所以你现在有一个悬空指针存储在 p 中(在 uint8_t 指针中),使用它(最迟会在你的智能指针尝试删除其数据时发生)将导致未定义的行为。

【讨论】:

    【解决方案3】:

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

    如果您需要将Image 转换为PtrImage,只需说您想从T 构建shared_ptr&lt;T&gt;。有两种标准方式:复制或移动,以及@987654324 @ 拥有其对象的所有权。

    在您的示例中,由于Image 仅返回左值引用,因此您只能使用副本。您可以通过拥有data 成员的所有权来从VecImage 迁移。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多