共享空指针。为什么这有效?

发布于 2024-10-14 23:40:40 字数 912 浏览 4 评论 0原文

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

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

std::shared_ptr<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 中工作,但是在其他编译器上也能正常工作吗?在其他平台上?

To solve a very peculiar problem in my application I need a shared-pointer to allocated data, but to the outside world, the underlying data type should remain hidden.

I could solve this by making some kind of Root class of which all my other classes inherit, and use a shared_ptr on this Root class, like this:

std::shared_ptr<Root>

However:

  • I don't want all my classes to inherit from this Root class just to be able to have this shared pointer
  • Sometimes I want to return a shared pointer to std::vector, or std::list, or std::set, ... which obviously don't inherit from my Root class

Strange enough, it seems that you can create a shared_ptr on void and this seems to work correctly, like shown in this example:

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 is correctly deleted and in a larger experiment I could verify that the shared pointer does indeed what it needs to do (delete x afer the last shared_ptr turns out the light).

Of course this solves my problem, since I can now return data with a SharedVoidPointer data member and be sure that it's correctly cleaned up where it should be.

But is this guaranteed to work in all cases? It clearly works in Visual Studio 2010, but does this also work correctly on other compilers? On other platforms?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

囍孤女 2024-10-21 23:40:40

您使用的 shared_ptr 构造函数实际上是一个构造函数模板,如下所示:

template <typename U>
shared_ptr(U* p) { }

它知道构造函数内部指针的实际类型是什么 (X) 并使用此信息创建一个可以正确删除指针并确保调用正确的析构函数的函子。这个函子(称为shared_ptr的“删除器”)通常与用于维护对象的共享所有权的引用计数一起存储。

请注意,只有当您将正确类型的指针传递给 shared_ptr 构造函数时,这才有效。如果您说:

SharedVoidPointer sp1(static_cast<void*>(x));

那么这将不起作用,因为在构造函数模板中,U 将是 void,而不是 X。那么该行为将是未定义的,因为不允许您使用 void 指针调用delete

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

The shared_ptr constructor that you use is actually a constructor template that looks like:

template <typename U>
shared_ptr(U* p) { }

It knows inside of the constructor what the actual type of the pointer is (X) and uses this information to create a functor that can correctly delete the pointer and ensure the correct destructor is called. This functor (called the shared_ptr's "deleter") is usually stored alongside the reference counts used to maintain shared ownership of the object.

Note that this only works if you pass a pointer of the correct type to the shared_ptr constructor. If you had instead said:

SharedVoidPointer sp1(static_cast<void*>(x));

then this would not have worked because in the constructor template, U would be void, not X. The behavior would then have been undefined, because you aren't allowed to call delete with a void pointer.

In general, you are safe if you always call new in the construction of a shared_ptr and don't separate the creation of the object (the new) from the taking of ownership of the object (the creation of the shared_ptr).

寂寞美少年 2024-10-21 23:40:40

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

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

James McNellis 清楚地解释了为什么 shared_ptr 有效,但是这里还有其他有趣的事情:假设您遵循 记录的最佳实践,在调用 new 时始终使用以下形式...

shared_ptr<T> p(new Y);

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

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

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

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

I think the implicit point of the question was that you can't delete by void*, so it seems strange that you can delete through shared_ptr<void>.

You can't delete an object via a raw void* primarily because it wouldn't know what destructor to call. Using a virtual destructor doesn't help because void doesn't have a vtable (and thus no virtual destructor).

James McNellis clearly explained why shared_ptr<void> works, but there is something else interesting here: Assuming you follow the documented best practice to always use the following form when invoking new...

shared_ptr<T> p(new Y);

...it is not necessary to have a virtual destructor when using shared_ptr. This is true whether T is void, or in the more familiar case where T is a polymorphic base of Y.

This goes against a long-standing conventional wisdom: That interface classes MUST have virtual destructors.

The OP's delete (void*) concern is solved by the fact that the shared_ptr constructor is a template that remembers the data type that it needs to destruct. This mechanism solves the virtual destructor problem in exactly the same way.

So even though the actual type of the object is not necessarily captured in the type of the shared_ptr itself (since T does not have to be the same type as Y), nevertheless, the shared_ptr remembers what type of object it is holding and it performs a cast to that type (or does something equivalent to that) when it comes time to delete the object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文