问:编写这个类的析构函数的正确和安全的方法是什么?

时间:2016-04-18 17:34:54

标签: c++ qt dynamic destructor dealloc

我在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<QPushButton*> list,将所有新分配的按钮添加到列表中(在构造函数中)并使用上面的list在析构函数中逐个删除它们。但这似乎是幼儿园的方法。

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

4 个答案:

答案 0 :(得分:5)

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

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

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

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

瞄准following,似乎(这是我自己的经验),他可能会删除子窗口小部件,并且他们将自行删除。有人提到过,可以将子级的父级设置为null,但这不是必需的,除非有人想要删除父级并使子级保持活动状态(重新父级...)。

但是,如example的最后一段所示,实例化的顺序很重要。如果在子节点之后实例化父节点,并且显式设置了子节点的父节点,则子节点将保留指向父节点的死引用/无效指针,并且当子节点尝试从子节点移除时,将发生未定义的行为。超出范围的父母。

除了Qt的documentation之外,还可以查看QObject :: setParent here (setParent_helper)的实现。由此可以看出,他们付出了巨大的努力,允许儿童/父母的删除不会发生意外,除了上述案例。

答案 1 :(得分:3)

来自QWidget::setLayout

  

QWidget将获得布局的所有权。

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

  

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

你不必清理任何东西。通过布局管理小部件
addWidgetremoveWidget / removeItem)。

答案 2 :(得分:3)

您必须确保将所有小部件挂钩到Qt&#39; object tree中,然后将为您解决破坏问题。你可以通过在构造它时给它一个父项来做到这一点。就像你对SignalMapper所做的一样。

答案 3 :(得分:1)

没有&#34;正确&#34;这是不正确的。析构函数。 一个析构函数 - 由编译器生成的析构函数 - 析构函数执行释放资源所需的一切。它应该是怎样的,以及应该如何设计现代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,或者编写一个类似的资源管理类,然后可以使用它而不必担心手动管理句柄的生命周期。