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

88
为什么有关Qt库的官方示例和教程从不使用智能指针?我只看到使用newdelete来创建和销毁小部件。
我搜索了理由,但没有找到,除非是出于历史原因或向后兼容性:并非每个人都希望程序在窗口小部件构造失败时终止,并且通过try/catch块处理这种情况只是丑陋的(即使在少数地方使用)。父窗口小部件可能接管子项的所有权也只是部分地解释了这个问题,因为你仍然必须在某个级别上使用delete来删除父窗口小部件。

10
在现代 C++ 出现之前,Qt 就已经拥有了智能指针,并且它们被用在一些项目中。你在这些示例中看不到它们的原因是因为 QObject 处理所有与子对象/父对象相关的问题和它们的生命周期。想要了解更多关于 Qt 智能指针的信息,请访问此链接 -> https://wiki.qt.io/Smart_Pointers - sanjay
2
你仍然必须在某个级别使用delete来删除父对象。但是,通常根对象位于main()中,所以它将被自动收集。 - dtech
看起来 QPointer 可以在不同的上下文中透明地使用:即如果引用的小部件在 QPointer 超出范围之前未添加到父对象中,则它的行为类似于“作用域指针”,但是,另一方面,如果已将小部件添加到父对象中,则会保留小部件的生命周期。 - Martin
不像普通指针被弃用或者说什么的,它们和以前一样有用。与某些人可能说的相反,仍然完全可以使用它们,在Qt的情况下,有很多保护措施来避免普通指针的“尖锐角落”。但是,是的,让我们现在停止使用指针,因为C++最终决定合并智能指针。 - dtech
5
值得指出的是,专家建议不是“永远不要使用裸指针”,而是“避免拥有裸指针”。这是一个重要的区别,你似乎没有意识到。 - Tim Seguine
显示剩余5条评论
5个回答

77

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

实际上,在共享/独占指针中使用QObject是一种过度设计(99%的情况下)。

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

话虽如此,你仍然可以使用RAII与Qt。例如QPointer行为类似于QObject的弱引用。我会使用QPointer<QWidget>而不是QWidget*

注意:为了不显得太狂热,两个词:Qt + valgrind。

2
  1. 如果父对象被删除,那么 shared_ptr 和 unique_ptr 将具有悬空指针。这可以通过 QPointer 解决,该指针将自动被指向的 QObject 清除。
- ratchet freak
对于没有父对象的非窗口小部件对象,是否仍然需要调用deleteLater而不是简单地删除该对象?我经常使用智能指针来管理QObject派生类的实例,因此我之前搜索过答案,以确保我没有做危险的事情,据我所知,文档并未表明通常删除QObject指针是错误的(即使它由父对象拥有,因为析构函数将删除父对象的引用)。 - Kyle Strand
2
文档警告您不要这样做:直接调用delete可能会因为对象(或其子对象)仍有挂起的事件/队列连接而导致崩溃。 - UmNyobe
@UmNyobe 这是真的,如果你从另一个 QThread 中删除了一个 QObjectQObject 的析构函数会自动断开所有与其相关的信号,并从其事件队列中删除挂起的事件。因此,如果您在其线程内部调用 delete,那么这是可以的。话虽如此,使用 deleteLater() 几乎不可能出错,除非您有某种原因需要立即清理。 - Ralph Tandetzky
根据QPointer文档,它与RAII无关。它仅保证如果其他人删除它所指向的QObject,则指针将为0。但它从不删除QObject本身。如果您想使用RAII,QSharedPointer应该是首选方法。 - Fritz

31

针对子对象的智能指针

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

孤立的裸指针

然而,当在堆上创建一个没有父级QObjectQObject(或派生类型)时,情况就非常不同了。在这种情况下,您不应只持有裸指针,而应持有智能指针,最好是一个指向该对象的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文档能够更加明确地阐述这一点。似乎有很多关于子对象被“自动”删除的引用,但却没有太多关于孤儿对象应该如何处理的信息。 - Kyle Strand
1
+1,特别是最后一段。我见过很多人不遗余力地尝试在任何地方都使用智能指针(特别是在参数传递方面,请参阅核心指南)。 - andreee

12
你已经回答了自己的问题:除非出于历史原因/向后兼容性。像QT这样庞大的库无法假设每个使用库的人都有支持C++11的编译器。newdelete在早期标准中是有保证存在的。

然而,如果你确实有支持使用智能指针的能力,我鼓励你使用它们而不是裸指针。


然而,在C++11之前和可能在QT之前就已经存在了智能指针。这是从一开始就做出的错误设计决策吗? - Martin
22
当Qt和一些其他库被设计和实现时,C++甚至还没有标准的字符串类。 - UmNyobe
1
如果在C++11之前智能指针意味着auto_ptr,那么不使用它是正确的决定。在C++11之前没有好的标准化智能指针。 - Zyx 2000
2
没有很好的标准化的指针,但有一些基于Qt的指针,例如QSharedPointerQScopedPointer - Ruslan
1
这个答案已经过时了,因为现代的Qt需要C++11支持。 - Mikhail

10

除了@Jamey所说的:

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


2
你需要在创建每个子窗口时立即将其添加到主窗口中,或者至少在可能引发异常的任何操作之前添加,否则程序将无法正常工作。这是一种我希望避免记住的限制。 - Martin
2
@Martin 在Qt应用程序中通常不会抛出异常(除非你是一个真正的粉丝),所以来吧 :) - mlvljr

5
  • QObject有一个已定义的父级,并且程序的树状结构可以有效地管理内存。

  • Qt中的动态性会破坏这个好的理想,例如传递一个原始指针。很容易就会持有一个“悬挂指针”,但这是编程中常见的问题。

  • Qt智能指针实际上是一种弱引用,即QPointer<T>,并提供了一些STL的便利功能。

  • 也可以与std::unique_ptr等混合使用,但应仅用于程序中的非Qt机制部分。


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