共享的void指针。为什么这样做?

时间:2011-01-26 16:48:09

标签: c++ visual-c++ c++11 shared-ptr smart-pointers

为了解决我的应用程序中一个非常特殊的问题,我需要一个指向已分配数据的共享指针,但对于外部世界,底层数据类型应该保持隐藏状态。

我可以通过创建我的其他类继承的某种Root类来解决这个问题,并在这个Root类上使用shared_ptr,如下所示:

std::shared_ptr<Root>

然而:

  • 我不希望我的所有类都继承自这个Root类,只是为了能够拥有这个共享指针
  • 有时候我想返回一个指向std :: vector或std :: list或std :: set,...的共享指针,它显然不会从我的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,但这是否也可以在其他编译器上正常工作?在其他平台上?

2 个答案:

答案 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会记住它所持有的对象类型,并且在删除对象时执行对该类型的强制转换(或执行与此类似的操作)。