【问题标题】:Shared void pointers. Why does this work?共享 void 指针。为什么这行得通?
【发布时间】:2011-06-15 23:46:06
【问题描述】:

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

我可以通过创建我所有其他类都继承的某种 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 被正确删除,在一个更大的实验中,我可以验证共享指针确实做了它需要做的事情(在最后一个 shared_ptr 熄灭后删除 x)。

这当然解决了我的问题,因为我现在可以使用 SharedVoidPointer 数据成员返回数据,并确保它在应有的位置被正确清理。

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

【问题讨论】:

  • how boost::~shared_ptr works? 的可能重复项
  • @Charles,这个问题看起来很相似,但在我的问题中,我明确要求避免使用 void 指针(不是指向基类的指针),以及这是否由标准保证(而不是为什么它适用于提升)。
  • @Patrick:这完全是同一个问题。删除器是在智能指针取得指针所有权时构造的,此时你传入的指针的静态类型信息用于创建删除器。
  • 难道没有隐藏的第二个假设吗?那个问题是关于shared_ptr&lt;Base&gt;;这个关于shared_ptr&lt;void&gt;。据推测,这两种方法都有效,因为存在从Derived*Base*void* 的隐式转换。因此,答案的一个重要部分必须是每个(非函数)指针类型都隐式转换为void*。我怀疑这不是真的。 const void* 没有。
  • @Patrick:错了。实际上,您可以删除 const 指针和指向 const 对象的指针。

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


【解决方案1】:

您使用的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

一般来说,如果您在构造 shared_ptr 时始终调用 new 并且不将对象的创建(new)与获取对象的所有权(创建shared_ptr)。

【讨论】:

  • C++0x 标准能保证这样工作吗?还是仅仅因为它是微软这样实现的?
  • @Patrick:它在 C++0x(以及 C++ TR1 和 Boost)中得到保证。
  • 如果您正在处理对象被破坏时要调用的 HANDLE 或 FILE,您甚至可以传递自定义关闭函数。
  • @JamesMcNellis 我刚刚发布了similar question,如果您能提供帮助,那就太好了!
【解决方案2】:

我认为问题的隐含点是你不能通过void*删除,所以你可以通过shared_ptr&lt;void&gt;删除似乎很奇怪。

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

James McNellis 清楚地解释了为什么shared_ptr&lt;void&gt; 有效,但这里还有一些有趣的地方:假设您遵循documented best practice 在调用new 时始终使用以下形式...

shared_ptr<T> p(new Y);

...使用 shared_ptr 时不必有虚拟析构函数。无论 Tvoid,还是在更熟悉的情况下 TY 的多态基,这都是正确的。

这与长期以来的传统观念背道而驰:That interface classes MUST have virtual destructors.

OP 的delete (void*) 问题通过 shared_ptr 构造函数是一个模板这一事实得到解决,该模板记住它需要销毁的数据类型。这种机制以完全相同的方式解决了虚拟析构函数问题。

因此,即使对象的实际类型不一定在 shared_ptr 本身的类型中捕获(因为 T 不必与 Y 的类型相同),然而,shared_ptr 会记住它所持有的对象类型,并在删除对象时执行对该类型的强制转换(或执行等效的操作)。

【讨论】:

  • 注意:Boost 中的实现实际上记住了一个指向 BOTH 类型(TY)的指针。它为您提供T* 用于正常操作,例如T* get(),但在删除对象时使用Y*
  • 以下答案提供了关于 shared_pointer 包含两个可能是不同类型(甚至不同值)的指针的额外上下文:stackoverflow.com/questions/6826402/…
  • @nobar 我刚刚发布了一个similar question,如果你能帮忙就太好了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多