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

Pat*_*ick 20 c++ smart-pointers shared-ptr visual-c++ c++11

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

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

std::shared_ptr<Root>
Run Code Online (Sandbox Code Playgroud)

然而:

  • 我不希望我的所有类都从这个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);
}
Run Code Online (Sandbox Code Playgroud)

x被正确删除,在一个更大的实验中,我可以验证共享指针确实完成了它需要做的事情(删除x,最后一个shared_ptr结束了灯光).

当然,这解决了我的问题,因为我现在可以使用SharedVoidPointer数据成员返回数据,并确保它正确地清理它应该在哪里.

但这是否可以保证在所有情况下都有效?它显然适用于Visual Studio 2010,但这是否也可以在其他编译器上正常工作?在其他平台上?

Jam*_*lis 27

shared_ptr您使用的构造函数实际上是一个构造函数模板,看起来像:

template <typename U>
shared_ptr(U* p) { }
Run Code Online (Sandbox Code Playgroud)

它知道构造函数内部指针的实际类型是什么(X)并使用此信息来创建一个能够正确delete指向指针并确保调用正确析构函数的仿函数.这个仿函数(称为shared_ptr"删除者")通常与用于维护对象共享所有权的引用计数一起存储.

请注意,这仅在将正确类型的指针传递给shared_ptr构造函数时才有效.如果你反而说:

SharedVoidPointer sp1(static_cast<void*>(x));
Run Code Online (Sandbox Code Playgroud)

那么这不会有效,因为在构造函数模板Uvoid,不是X.然后行为将是未定义的,因为不允许delete使用void指针调用.

一般来说,如果你总是调用new构造a shared_ptr并且不将对象(the new)的创建与获取对象的所有权(创建shared_ptr)分开,那么你是安全的.


nob*_*bar 10

我认为问题的隐含点是你不能删除void*,所以你可以通过删除看起来很奇怪shared_ptr<void>.

您不能通过raw删除对象void*主要是因为它不知道要调用的析构函数.使用虚拟析构函数没有帮助,因为void没有vtable(因此没有虚拟析构函数).

James McNellis清楚地解释了为什么shared_ptr<void>有效,但这里有一些有趣的东西: 假设您遵循记录的最佳实践,在调用时始终使用以下形式new...

shared_ptr<T> p(new Y);
Run Code Online (Sandbox Code Playgroud)

... 使用shared_ptr时没有必要使用虚拟析构函数.无论Tvoid,还是在更熟悉的情况下,TY的多态基.

这违背了长期的传统观念:接口类必须具有虚拟析构函数.

由于delete (void*)shared_ptr构造函数是一个记住它需要破坏的数据类型的模板,因此解决了OP的问题.该机制以完全相同的方式解决了虚拟析构函数问题.

因此,即使对象的实际类型不一定是以shared_ptr本身的类型捕获的(因为T不必与Y的类型相同),但是,shared_ptr会记住它所持有的对象类型并执行当删除对象时,转换为该类型(或执行与此类似的操作).