为什么官方的Qt示例和教程不使用智能指针?


76

为什么有关Qt库的官方示例和教程从不使用智能指针?我只看到newdelete用于创建和销毁小部件。

我搜索了基本原理,但找不到它,除非是出于历史原因或向后兼容,否则我自己也看不到:不是每个人都希望程序在小部件构造函数失败时终止,并通过try / catch处理它块是丑陋的(即使在很少的地方使用)。父母小部件可能拥有孩子的所有权这一事实也仅向我部分解释了这一点,因为您仍必须delete在某种程度上为父母使用。


9
Qt在现代c ++之前就有了智能指针。这些用于某些项目中。在这些示例中您没有看到,因为QObject可以处理所有与孩子/父母有关的问题,并且可以解决生命周期。有关Qt智能指针的更多信息,请访问此-> wiki.qt.io/Smart_Pointers
sanjay 2015年

2
“在某种程度上,您仍然必须对父级使用delete”-不一定,因为根对象通常位于其中,main()因此它将被自动收集。
dtech

看起来QPointer可以在不同的上下文中透明使用:即,如果在QPointer超出范围之前尚未将引用的窗口小部件添加到父对象,则它的行为就像“作用域指针”如果已将其添加到父项中。QPointer超出范围之前
马丁

这不像不赞成使用普通指针之类的东西,它们像以前一样有用。与某些人可能会说的相反,使用它们仍然是完全可以的,并且在Qt的情况下,在纯指针的“尖角”周围有很多保护措施。但是,是的,让我们现在就停止使用指针,只是因为C ++最终决定合并智能指针。
dtech

5
@ddriver值得指出的是,专家建议并非“永远不要使用原始指针”。建议是“避免拥有原始指针”。这是您似乎没有做的重要区别。
蒂姆·塞吉恩

Answers:


67

因为Qt依赖于父子模型来管理Qobject资源。它遵循复合+责任链模式,从事件管理到内存管理,绘图,文件处理等使用...

实际上,尝试在共享\唯一指针中使用QObject是过度设计的(99%的时间)。

  1. 您必须提供一个自定义删除器,该删除器将调用deleteLater
  2. 带有父对象的qobject在父对象中已经具有引用。因此,您知道只要父对象存在,对象就不会泄漏。当您需要摆脱它时,可以deleteLater直接致电。
  3. 没有父项的QWidget在Qapplication对象中已经具有引用。与第2点相同。

也就是说,您仍然可以将RAII与Qt一起使用。例如,QPointer充当上的弱引用QObject。我会使用QPointer<QWidget>而不是QWidget*

注意:不要听起来太狂热,两个词:Qt + valgrind。


2
要点4.如果删除了父对象,则shared_ptr和unique_ptr将有一个悬空指针。这是由QPointer解决的,QPointer将由指向的QObject自动清除。
棘轮怪胎

对于没有父母的非小物件对象,是否仍然需要调用deleteLater而不是简单地delete将其对象化?我经常使用智能指针来管理QObject派生类的实例,因此我一直在寻找答案,以确保我没有做任何危险的事情,并且据我所知文档未表明它是delete一个QObject指针正常(即使它由父对象拥有的,因为析构函数将删除父参考)。
凯尔·斯特兰德

2
文档警告您不要这样做。直接调用delete可能会由于与对象(或其子对象)的未决事件\排队连接而导致崩溃。
UmNyobe 2015年

@UmNyobe是的,如果您QObject从另一个中删除一个QThread。的析构函数QObject断开与之的所有信号连接,并自动从其事件队列中删除未决事件。因此,调用deleteQObject是好的,如果你从它的线程内做到这一点。话虽如此,deleteLater()除非您出于某种原因需要立即清理,否则您几乎不会做任何错误的事情。
拉尔夫·坦德斯基

@UmNyobe根据QPointer文档,它与RAII无关。它仅保证如果其他人删除了它指向的QObject,则指针将为0。但是它永远不会删除QObject本身。如果您是RAII,则应首选QSharedPointer。
Fritz

26

儿童智能指针

智能指针类std::unique_ptrstd::shared_ptr用于内存管理。拥有这样的智能指针意味着您拥有该指针。但是,在QObjectQObject父级一起创建或派生类型时,所有权(清理的责任)将移交给父级QObject。在那种情况下,标准库智能指针是不必要的,甚至是危险的,因为它们可能导致双重删除。kes!

指向孤儿的原始指针

但是,在QObject没有父项的QObject情况下在堆上创建(或派生类型)时,情况会大不相同。在这种情况下,您不仅应该持有原始指针,还应该拥有智能指针,最好是指向std::unique_ptr对象的指针。这样,您将获得资源安全性。如果以后将对象所有权交给父母QObject,则可以使用std::unique_ptr<T>::release(),如下所示:

auto obj = std::make_unique<MyObject>();
// ... do some stuff that might throw ...
QObject parentObject;
obj->setParent( &parentObject );
obj.release();

如果您在给孤儿父母之前做的事情抛出异常,那么,如果使用原始指针来保存对象,则可能会发生内存泄漏。但是上面的代码可以避免这种泄漏。

一般而言

现代的C ++建议不是避免所有原始指针,而是避免拥有原始指针。我可能会添加另一条现代的C ++建议:不要对其他程序实体拥有的对象使用智能指针。


4
是。这个。所有权就是一切。我希望Qt文档对此更加明确;似乎有很多关于“自动”删除儿童的参考,但是没有很多有关应该如何处理孤儿的信息。
凯尔·斯特兰德

+1,尤其是最后一段。我见过吨场合人们不懈地尝试使用智能指针等。无论他们(特别是参数传递,也看到这个核心指导原则。
andreee

13

您已经回答了自己的问题:except if it's for historic reasons/backward compatibility。像QT一样庞大的库不能假设使用该库的每个人都有支持C ++ 11的编译器。newdelete保证存在于较早的标准中。

但是,如果您确实支持使用智能指针,那么我建议您将其用于原始指针。


但是,智能指针存在于C ++ 11之前,也可能存在于QT之前。从一开始就是一个错误的设计决定?
马丁

21
错误...设计了qt和其他一些库\实现时,c ++甚至没有标准的字符串类。
UmNyobe 2015年

如果C ++ 11之前的智能指针表示auto_ptr,那么不使用它是正确的决定。C ++ 11之前没有好的标准化智能指针。
Zyx 2000

2
没有好的标准化的,但是有些基于Qt的,例如QSharedPointerQScopedPointer
Ruslan 2015年

这个答案是过时的,因为现代Qt需要C ++ 11支持。
米哈伊尔(Mikhail)'18

10

除了@Jamey所说的:

如果您设计巧妙,则可能永远不必在小部件上使用删除功能。假设您有一个主窗口,并且正在为其创建一个自动对象,并在事件循环中运行该窗口。现在,该小部件中的其余所有项目都可以作为其子级添加。并且由于您是直接/间接将它们作为子级添加到此MainWindow中,因此当您关闭此主窗口时,所有内容都会自动处理。只需确保您创建的所有动态对象/小部件都是MainWindow的子代/孙代即可。因此,无需显式删除。


2
您必须在创建主窗口后立即将每个子窗口添加到主窗口中,或者至少在可能引发任何异常或该东西不起作用之前将其添加到主窗口中,这是我要避免记住的限制条件
Martin

2
@Martin通常不会在Qt应用程序中抛出异常(除非是真正的粉丝,所以来吧:)
mlvljr

5
  • QObject 具有定义的父项,程序的树状结构允许非常有效地管理内存。

  • Qt中的动态性打破了这个理想,例如传递原始指针。人们很容易最终持有dangling pointer,但这是编程中的常见问题。

  • Qt智能指针实际上是一个弱引用,它QPointer<T>
    提供了一些STL糖果。

  • 一个也可以与之混用std::unique_ptr,但应仅将其用于程序中的非Qt机械。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.