为了解决我的应用程序中一个非常特殊的问题,我需要一个指向已分配数据的共享指针,但对于外部世界,底层数据类型应该保持隐藏状态。
我可以通过创建我的其他类继承的某种Root类来解决这个问题,并在这个Root类上使用shared_ptr,如下所示:
std::shared_ptr<Root>
然而:
奇怪的是,似乎你可以在void上创建一个shared_ptr,这似乎工作正常,如下例所示:
class X
{
public:
X() {std::cout << "X::ctor" << std::endl;}
virtual ~X() {std::cout << "X::dtor" << std::endl;}
};
typedef std::shared_ptr<void> SharedVoidPointer;
int main()
{
X *x = new X();
SharedVoidPointer sp1(x);
}
x被正确删除,在一个更大的实验中,我可以验证共享指针确实完成了它需要做的事情(删除x,最后一个shared_ptr结束了灯光)。
当然这解决了我的问题,因为我现在可以使用SharedVoidPointer数据成员返回数据,并确保它已正确清理到应该的位置。
但这可以保证在所有情况下都有效吗?它显然适用于Visual Studio 2010,但这是否也可以在其他编译器上正常工作?在其他平台上?
答案 0 :(得分:27)
您使用的shared_ptr
构造函数实际上是一个构造函数模板,如下所示:
template <typename U>
shared_ptr(U* p) { }
它知道构造函数内部指针的实际类型是什么(X
),并使用此信息创建一个能够正确delete
指针并确保调用正确析构函数的仿函数。这个仿函数(称为shared_ptr
的“删除者”)通常与用于维护对象共享所有权的引用计数一起存储。
请注意,这仅在将正确类型的指针传递给shared_ptr
构造函数时才有效。如果你反而说:
SharedVoidPointer sp1(static_cast<void*>(x));
然后这不起作用,因为在构造函数模板中,U
将是void
,而不是X
。然后,行为将被定义为未定义,因为不允许使用void指针调用delete
。
一般情况下,如果您始终在构建new
时调用shared_ptr
并且不将对象(new
)的创建与对象的所有权(创建shared_ptr
)。
答案 1 :(得分:10)
我认为问题的隐含点是您无法通过void*
删除,因此您可以通过shared_ptr<void>
删除,这似乎很奇怪。
您无法通过原始void*
删除对象,主要是因为它不知道要调用的析构函数。使用虚拟析构函数没有用,因为void
没有vtable(因此没有虚析构函数)。
James McNellis清楚地解释了为什么shared_ptr<void>
有效,但是这里还有其他一些有趣的事情:假设您在调用{{1}时遵循documented best practice总是使用以下形式}} ...
new
... 使用shared_ptr 时没有必要使用虚拟析构函数。无论 T 是shared_ptr<T> p(new Y);
还是更熟悉的情况, T 是 Y 的多态基础,都是如此。
这违背了长期的传统观念:That interface classes MUST have virtual destructors.
OP的void
问题通过shared_ptr构造函数是记住需要销毁的数据类型的模板来解决。这种机制以完全相同的方式解决了虚拟析构函数问题。
所以尽管对象的实际类型不一定是在shared_ptr本身的类型中捕获的(因为 T 不必与 Y 的类型相同但是,shared_ptr会记住它所持有的对象类型,并且在删除对象时执行对该类型的强制转换(或执行与此类似的操作)。