共享空指针。为什么这有效?
为了解决我的应用程序中的一个非常特殊的问题,我需要一个分配数据的共享指针,但对于外界来说,底层数据类型应该保持隐藏。
我可以通过创建某种根类来解决这个问题,我的所有其他类都继承该根类,并在这个根类上使用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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您使用的
shared_ptr
构造函数实际上是一个构造函数模板,如下所示:它知道构造函数内部指针的实际类型是什么 (
X
) 并使用此信息创建一个可以正确删除指针并确保调用正确的析构函数的函子。这个函子(称为shared_ptr的“删除器”)通常与用于维护对象的共享所有权的引用计数一起存储。请注意,只有当您将正确类型的指针传递给
shared_ptr
构造函数时,这才有效。如果您说:那么这将不起作用,因为在构造函数模板中,
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: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 correctlydelete
the pointer and ensure the correct destructor is called. This functor (called theshared_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:then this would not have worked because in the constructor template,
U
would bevoid
, notX
. The behavior would then have been undefined, because you aren't allowed to calldelete
with a void pointer.In general, you are safe if you always call
new
in the construction of ashared_ptr
and don't separate the creation of the object (thenew
) from the taking of ownership of the object (the creation of theshared_ptr
).我认为问题的隐含要点是你不能通过
void*
删除,所以你可以通过shared_ptr
删除似乎很奇怪。您无法通过原始
void*
删除对象,主要是因为它不知道要调用哪个析构函数。使用虚拟析构函数没有帮助,因为void
没有虚函数表(因此没有虚拟析构函数)。James McNellis 清楚地解释了为什么
shared_ptr
有效,但是这里还有其他有趣的事情:假设您遵循 记录的最佳实践,在调用new
时始终使用以下形式......使用shared_ptr时不需要有虚拟析构函数。无论T是
void
,还是在更熟悉的情况下(T是Y的多态基),这都是正确的。这违背了长期以来的传统观点:接口类必须具有虚拟析构函数。
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 throughshared_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 becausevoid
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 invokingnew
......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.