你可以使用删除器的成员来保持 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::mData
与 pIm
共享。
注意:我假设 GetData()
返回的向量具有与 Image
相同(或更长)的生命周期(对于 VecImage
)。如果它是一个不相关的向量(来自其他对象),那么你将没有解决方案。
如评论中所述,vector 不应重新分配两者
你甚至不应该尝试让共享指针猜测它是否应该删除它的对象。如果你继续这样做,你会一次被一个角落案例抓住,或者即使你设法找到所有,一个明显无关的变化创建的新案例。
如果您需要将 Image
转换为 PtrImage
,只需说明您想从 T 构建 shared_ptr<T>
。有两种标准方法:复制或移动,并且 shared_ptr
拥有其对象的所有权。
在你的例子中,由于Image
只有returns一个左值引用,你只能使用一个副本。您可以通过取得其 data
成员的所有权从 VecImage
移动。
给定以下类型
// 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::mData
与 pIm
共享。
注意:我假设 GetData()
返回的向量具有与 Image
相同(或更长)的生命周期(对于 VecImage
)。如果它是一个不相关的向量(来自其他对象),那么你将没有解决方案。
如评论中所述,vector 不应重新分配两者
你甚至不应该尝试让共享指针猜测它是否应该删除它的对象。如果你继续这样做,你会一次被一个角落案例抓住,或者即使你设法找到所有,一个明显无关的变化创建的新案例。
如果您需要将 Image
转换为 PtrImage
,只需说明您想从 T 构建 shared_ptr<T>
。有两种标准方法:复制或移动,并且 shared_ptr
拥有其对象的所有权。
在你的例子中,由于Image
只有returns一个左值引用,你只能使用一个副本。您可以通过取得其 data
成员的所有权从 VecImage
移动。