C++11中如何使用非拥有引用/指针访问unique_ptr?

9

这不是一个“如何做”而是“如何以正确的方式做”的问题。

我正在使用Qt开发一个编辑器,其中不同的小部件会显示子级及其成员变量。每个小部件都应该持有对编辑的子对象的引用/指针,以显示和更改它们的成员变量。

第一次尝试是我学过(并且还在使用)的旧ANSI C方式,即使用简单的原始指针来引用已使用的对象。虽然这种方法很好用,但由于C++11标准支持智能指针并建议使用它们,我正在尝试使用它们。

问题是,我不太确定在这种情况下应该使用哪种智能指针……阅读了智能指针:谁拥有你?我什么时候使用哪种指针?和其他一些文章后,我得出了不同的结论:

第一种方法是使用*unique_ptr,因为明显被编辑的对象是所有者,它创建并删除其子项。小部件只是引用子项以显示或更改它们。

问题在于小部件应该如何引用子项......

目前我只是使用通过unique_ptrget()方法得到的原始指针,但这似乎有些不完善。我仍然可能意外调用指针的delete函数并取消智能指针的好处。
第二种方法是使用shared_ptr,因为许多对象都引用并编辑它。同时,在一个小部件中意外删除它也不会有任何影响,因为它仍然被其他对象拥有。 问题在于它们拥有它。当我想从已编辑的对象中删除它时,我还必须通知所有小部件在它真正消失之前删除它。(这似乎又有缺陷且容易出错)
我对这两种方法都不太满意。是否有一种更清洁的方式来指向对象的unique_ptr子项?或者我错过了完全不同且更好的解决此问题的方法?

当被引用的对象被销毁时,您需要通知引用对象吗? - Benjamin Lindley
2
在这种情况下,我们真的需要“愚蠢智能指针”。 - Mark Garcia
@BenjaminLindley 目前他们需要被警告,删除相应的小部件/控件并在删除时不再使用它们。例如,一个小部件需要获取一个子元素的坐标以将其放置在OpenGL渲染视图中。但我真的尽量避免这种情况,因为每个小部件都需要对发出的信号做出反应。当所有者能够自行确定对象是否仍然存在时,这将更理想。 - nils277
3
如果你完全不使用delete,那么就永远不会“意外地”调用指针上的delete。因此,如果你仍然可能在指针上“意外地”调用delete,那只有在你确实使用了delete时才会发生。简而言之,禁止你的代码中所有对delete的使用即可。 - Nicol Bolas
对Nicol点个赞。你可能会意外地“删除”一个你不拥有的指针。你也可能会意外地调用abort()函数。意外是看人的眼光而定的。 - Potatoswatter
你使用unique_ptr的对象,它们是QObject的子类吗?如果是的话,请确保它们没有父对象,并考虑使用Qt的父子机制而不是智能指针,并考虑使用QPointer作为QObject的智能指针。如果它们不是QObject的子类,则可以使用std::的智能指针。 - hyde
4个回答

3
您的使用情况不能直接转化为要求(如果其他人在您编辑时删除了小部件怎么办?),但我假设您不需要除裸指针之外的任何内容。
标准库不提供任何严格的指针观察器类。观察实体包括:
- 可空的原生类型可变指针(T*) - 不可空的原生类型不可变引用(T&) - 不可空的类类型可变引用/代理(std::reference_wrapper) - 可空的自验证管理对象可变指针(std::weak_ptr)
如果您想要一个非空的,可变的指向非管理对象的指针,这是一件相当合理的事情,您可以自己编写代码。
但是裸指针并不那么糟糕。唯一的区别是它可以是nullptr,并且它没有一个漂亮、长、显式的名称在std命名空间中。

2
你想使用shared_ptr替换你的unique_ptrweak_ptr,这样可以完全满足你的需求。使用weak_ptr不会影响编辑对象删除底层对象的能力。

谢谢,这看起来就是我要找的东西。 - nils277

1
如果您正在使用Qt,您可能会考虑使用Qt智能指针而不是std::智能指针: 或者对于QObjects:
  • QPointer(或QWeakPointer,特别是在Qt4中)
或者为了实现写时复制数据共享,使用Qt容器和QString风格: 还有其他指针类,但以上一些最有可能做到你想要的。另外很重要的一点是,如果您的数据在Qt容器类中,如QStrings等,它们处理自己的内存,并具有写时复制语义,通常应该作为普通值(有时作为引用)传递,而不是指针。
但最重要的是,不要将std::unique_ptrstd::shared_ptr与具有父项的QObjects一起使用,因为如果父项先删除子项,则std::指针将再次删除它,导致程序崩溃(另一种方法将正常工作,子项将通知其父项已被删除)。换句话说,如果混合使用QObjects和std::指针,可能会出现微妙的错误,因此请不要这样做。从您的问题中不清楚是否正在这样做,只是以防万一提及。

你说得很好。我没想到QObject会和std::pointer有问题。我认为,由于我的对象派生自QObject以利用信号和槽,所以 QSharedPointerQWeakPointer 是我要走的路线。 - nils277
请注意,QSharedPointer 存在相同的问题,它没有针对由父对象删除的 QObject 的特殊支持。如果没有父对象,则没问题,但是如果有父对象,则同样适用。我想这是一种可以接受的方式,如果您的 QObjects 没有任何逻辑父/所有者,请小心记住保持这种状态(例如,不要有允许提供父对象的构造函数)。如果它们有父对象,则只需使用 QPointer 在其他地方保存弱引用即可。 - hyde

0

有一个提议针对世界上最愚蠢的智能指针,它是一个非拥有指针。基本上,它是一个T*,表示“哦,顺便说一下,我不对这个数据拥有任何所有权”。

在这种情况下,您必须管理观察/被动/愚蠢指针的重置,如果unique_ptr消失--半手动生命周期管理。在拥有上述功能之前,使用一个名称指示其为非拥有的原始T*同样有效。

做以上操作有好处,特别是当您拥有具有相关生命周期的对象时。

如果您不这样做,那么一个 shared_ptrweak_ptr 可以起作用。但是请注意,任何拥有 weak_ptr 的人都可以创建一个新的 shared_ptr,从而延长共享对象的生命周期超出原始的 shared_ptr。这必须发生,否则就会存在一种竞态条件,即使用 weak_ptr 的用户确保其数据有效,然后去使用它,但在他们这样做之前,shared_ptr 的数据已经被销毁。
因此,weak_ptr 必须能够防止 shared_ptr 被销毁(在多线程上下文中进行阻塞,在单线程上下文中以某种方式“失败”)。在这种情况下,“失败”包括扩展寿命,这解决了多线程和单线程问题。

单线程问题是,您验证 weak_ptr 是否好,执行某些导致 shared_ptr 重置的操作,然后继续希望使用 weak_ptr 的数据而不重新检查。天真地说,它可以通过异常避免,但仔细检查后,您会遇到问题:必须编写所有方法以在访问 this 之前检查删除并如果 this 被删除则抛出异常,这是一个相当严格的要求!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接