C++11对unique_ptr的非拥有引用/指针



这不是"如何做"的问题,而是"如何以正确的方式做"

我正在Qt中开发一个编辑器,其中不同的小部件显示子级及其(成员)变量。这些小部件中的每一个都应该保存一个指向编辑过的子部件的引用/指针,以显示和更改其成员变量。

第一次尝试是用旧的ANSI C方法学习(现在仍然有点坚持),使用指向所用对象的简单原始指针。它工作得很好,但由于C++11标准支持智能指针,并且建议使用它们,我正在尝试使用它们。

问题是,我不太确定在这种情况下使用它们的"最佳方式"是什么。。。看完《聪明的指针》:或者谁拥有你的孩子?我什么时候用哪种指针?和其他一些人,我得出了不同的结论:

第一种是使用*unique_ptr,因为编辑的对象显然是创建和删除其子对象的所有者。小部件只是指代子部件来显示或更改它们。问题是小部件应该如何引用子。。。

现在,我只是简单地使用unique_ptrget()方法得到的原始指针,但这对我来说似乎有点缺陷。我仍然可以意外地在指针上调用delete并取消智能指针的好处。

第二种方法是使用shared_ptr,因为许多对象都引用了子对象并对其进行编辑。此外,在一个小部件中意外删除它不会造成伤害,因为它仍然由其他对象所有。问题是他们拥有它。当我想从编辑过的对象中删除它时,我还必须在它真正消失之前通知所有小部件删除它。(这再次看起来有缺陷且容易出错)

我真的不满足于这两种方式。有没有一种干净的方法可以指向对象的unique_ptr子级?还是我错过了一个完全不同的、更好的方法来解决这个问题?

您的用例不会直接转化为需求(如果在您编辑小部件时其他人删除了它怎么办?)但我认为您不需要裸指针之外的任何东西。

标准库不提供任何严格的指针观察器类。观察员实体:

  • 本机类型的可为Null、可变指针(T *
  • 本机类型的不可为Null、不可变的引用(T &
  • 类类型(std::reference_wrapper<T>)的不可为null、可变引用/代理
  • 指向托管对象的可为Null、自验证、可变指针(std::weak_ptr<T>

如果您想要一个指向非托管对象的不可为null、可变的指针,这是一个相当合理的要求,那么您可以滚动自己的指针。

但裸指针并没有那么糟糕。唯一的区别是它可以是nullptr,并且在名称空间std中没有一个漂亮的、长的、明确的名称。

您希望使用shared_ptr来代替unique_ptr,使用weak_ptr来代替原始指针。这会给你想要的东西。weak_ptr不会干扰编辑对象删除底层对象的能力。

如果您正在使用Qt,您可能会考虑使用Qt智能指针,而不是std::smart-points:

  • QShardPointerQWeakPointer

或对于QObjects:

  • QPointer(或QWeakPointer,特别是在Qt4中)

或实现写时复制数据共享,Qt容器和QString风格:

  • QSharedDataPointer

还有其他指针类,但上面的一些类最有可能实现您想要的功能。同样重要的是,如果您的数据在Qt容器类QStrings或类似的类中,它们会使用写时复制语义处理自己的内存,并且通常应该作为纯值(有时作为引用)而不是指针传递。

但最重要的是,不要将std::unique_ptrstd::shared_ptr与具有父对象的QObjects一起使用,因为如果父对象首先删除子对象,那么std::指针将再次删除它,从而使程序崩溃(其他方式也可以,子对象将通知其父对象它已删除)。换句话说,如果你混合了QObjects和std::pointers,很有可能会出现微妙的错误,所以不要这样做。从你的问题中还不清楚你是否在这样做,只是以防万一。

有一个关于世界上最愚蠢的智能指针的提议,它是一个非拥有指针。基本上,它是一个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被删除,则抛出d、 非常苛刻的要求!)

最新更新