【问题标题】:Qt: What is the correct and safe way to write the destructor of this class?Qt:编写这个类的析构函数的正确和安全的方法是什么?
【发布时间】:2016-08-10 14:04:51
【问题描述】:

我在 Windows7 上使用 Qt5,最近发现 an interesting Qt example code

基本上是这样的:

ButtonWidget::ButtonWidget(const QStringList &texts, QWidget * parent) 
: QWidget(parent)
{
    signalMapper = new QSignalMapper(this);

    QGridLayout * gridLayout = new QGridLayout;
    for (int i = 0; i < texts.size(); ++i) 
    {
        QPushButton * button = new QPushButton(texts[i]);
        connect(button, SIGNAL(clicked()), signalMapper, SLOT(map()));
        signalMapper->setMapping(button, texts[i]);
        gridLayout->addWidget(button, i / 3, i % 3);
    }

    connect(signalMapper, SIGNAL(mapped(QString)), this, SIGNAL(clicked(QString)));

    setLayout(gridLayout);
}

这是一个不错且有用的示例,但它没有适当的析构函数...以防万一我想删除 ButtonWidget 类型的对象,或者如果我想自定义代码以能够删除/添加小部件。这个想法是如何删除在构造函数中创建的所有对象(动态地,使用new)。

我的方法是使用私有变量QList&lt;QPushButton*&gt; list,将所有新分配的按钮添加到列表中(在构造函数中)并在析构函数中一个一个地删除它们,使用上面的list。但这似乎是幼儿园的方法。

我认为必须有其他方法,更好的方法来做到这一点,没有列表,也不会弄乱构造函数代码:) 感谢您的时间和耐心!

【问题讨论】:

  • 您应该阅读 Qt 文档并了解 Qt 如何管理动态分配的对象。

标签: c++ qt dynamic destructor dealloc


【解决方案1】:

对于接收父类作为其构造函数的参数的 Qt 类,例如 QSignalMapper,重要的是要注意该类将自身添加到其父类的对象列表中,并且当其父类(QObject)被破坏时,它将被破坏。

因此,如果将对象传递给父对象,则无需执行任何操作。你的 cpp 文件中可能有一个空的析构函数,只是为了确保 QObject 可以使用成员的定义,但这取决于类。

但是,如果您确实选择编写自己的析构函数,则正确实现的典型 Qt“子”将在销毁时将自己从其父级中移除。

除了标题/标题中提出的问题外,OP 还询问如果他想在删除父对象之前删除子对象会发生什么:

看到following,似乎(这是我自己的经验)他可能只是删除子小部件,并且它们会自行删除。有人提到可以将孩子的父母设置为 null,但这不是必需的,除非您想删除父母并让孩子保持活力(重新养育...)。

但是,如example 的最后一段所示,实例化的顺序很重要。如果在子代之后实例化父代,并且子代的父代被显式设置,则指向父代的无效引用/无效指针将留给子代,当子代试图将自己从超出范围的父级。

除了Qt的documentation,还可以看看QObject::setParenthere (setParent_helper)的实现。从这里可以看出,除了提到的情况外,他们付出了巨大的努力来允许孩子/父母的删除不会发生意外。

【讨论】:

  • 好的,我明白了。但是如果我只需要一个简单地删除在构造函数中创建的按钮的方法呢?假设将来我不会销毁ButtonWidget 类型的整个对象。假设我只需要删除按钮,也许还需要另一种方法来添加新按钮?
  • 只需删除父级,或删除按钮。我的立场是正确的,但它应该可以正常工作。
  • 您必须自己进行管理。使用SomeQObject-&gt;setParent(0),您可以将对象与其父对象分离。然后你可以简单地删除它。
  • @WernerErasmus:这样的事情能胜任吗:QLayoutItem *child; while ((child = gridLayout-&gt;takeAt(0)) != 0) { delete child-&gt;widget(); delete child; }
  • 只要删除父级,它的子级会被自动删除...如果你删除一个子级,请确保将它的父级设置为null,正如@AndreasT 所提到的
【解决方案2】:

来自QWidget::setLayout

QWidget 将拥有布局的所有权。

来自QLayout::addItem(由QLayout::addWidget调用):

注意:项目的所有权转移到布局,布局负责删除它。


您不必清理任何东西。通过布局管理小部件
(addWidget, removeWidget/removeItem)。

【讨论】:

    【解决方案3】:

    您必须注意,将所有小部件挂接到 Qt 的 object tree,然后它会为您处理破坏。你可以通过在构造它时给它一个父级来做到这一点。就像你对 SignalMapper 所做的那样。

    【讨论】:

      【解决方案4】:

      没有“正确的”析构函数是不正确的。 一个析构函数——一个由编译器生成的——并且该析构函数做了所有必要的事情来释放资源。应该是这样,现代 C++ 代码也应该这样设计。

      正确设计的 C++ 类应该是可用的,而无需显式管理它们的资源。这里就是这样。

      此外,该示例不必要地动态分配可能只是类成员的成员。在 C++11 中也不需要信号映射器。我就是这样做的:

      class ButtonWidget : public QWidget {
        Q_OBJECT
        QGridLayout m_layout { this };
      public:
        ButtonWidget(const QStringList &items, QWidget *parent = 0);
        ~ButtonWidget();
        Q_SIGNAL void buttonClicked(const QString &);
      }
      
      ButtonWidget::ButtonWidget(const QStringList &items, QWidget *parent) 
      : QWidget(parent)
      {
        const int columns = 3;
        for (int i = 0; i < items.size(); ++i) {
          auto text = items[i];
          auto button = new QPushButton(text);
          connect(button, &QPushButton::clicked, [this, text]{
            emit buttonClicked(text);
          });
          m_layout.addWidget(button, i / columns, i % columns);
        }
      }
      
      ButtonWidget::~ButtonWidget() {}
      

      这是一个完整、可用的小部件,没有内存泄漏。这就是现代 C++/Qt 的样子。如果您需要在析构函数中做一些花哨的事情,您应该始终考虑将内存管理分解为它自己的 RAII 类。例如,不要在析构函数中手动关闭文件句柄,而是考虑使用QFile,或者编写一个类似的资源管理类,然后您可以使用它而无需担心手动管理句柄的生命周期。

      【讨论】:

        猜你喜欢
        • 2012-05-03
        • 2017-04-20
        • 1970-01-01
        • 2014-12-31
        • 1970-01-01
        • 2015-03-03
        • 1970-01-01
        • 1970-01-01
        • 2011-08-10
        相关资源
        最近更新 更多