Qt:编写此 class 的析构函数的正确且安全的方法是什么?
Qt: What is the correct and safe way to write the destructor of this class?
我在 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
类型的对象,或者如果我想自定义代码能够 remove/add 小部件。这个想法是如何删除在构造函数中创建的所有对象(动态地,使用 new
)。
我的方法是使用私有变量QList<QPushButton*> list
,将所有新分配的按钮添加到列表(在构造函数中)并在析构函数中逐个删除它们,使用上面的list
.但这似乎是幼儿园的做法。
我认为一定有其他更好的方法,无需列表,也不会弄乱构造函数代码:)感谢您的时间和耐心!
对于接收 parent 作为其构造函数参数的 Qt classes,例如 QSignalMapper,重要的是要注意 class 将自身添加到其 parent的 object 列表,当它的 parent (QObject)被销毁时,它将被销毁。
因此,如果您传递 object 和 parent,您无需执行任何操作。你的 cpp 文件中可能有一个空的析构函数,只是为了确保成员的定义对 QObject 可用,但这取决于 class.
但是,如果您确实选择编写自己的析构函数,则正确实现的典型 Qt "child" 会在销毁时从其 parent 中删除自身。
除了 caption/title 中提出的问题外,OP 询问如果他想在删除 parent 之前删除 sub-object 会发生什么:
看到 following,似乎(这是 iaw。我自己的经验)他可能只是删除了 child 小部件,并且它们会自行删除。已经提到可以将 child 的 parent 设置为空,但这不是必需的,除非有人想删除 parent 并保留 child 活着 (re-parenting...).
但是,如example最后一段所示,实例化的顺序很重要。如果 parent 在 child 之后实例化,并且 child 的 parent 被显式设置,则指向 parent 的无效引用/无效指针将是留下 child,当 child 试图从 out-of-scope parent.
中删除自身时,将在破坏 child 时发生未定义的行为
除了Qt的documentation, one can also look at the implementation of QObject::setParent here (setParent_helper)。从这里可以看出,除了提到的情况外,他们付出了很大的努力来确保 children/parents 删除不会发生任何意外。
The QWidget will take ownership of layout.
来自 QLayout::addItem
(由 QLayout::addWidget
调用):
Note: The ownership of item is transferred to the layout, and it's the layout's responsibility to delete it.
您不必清理任何东西。通过布局管理小部件
(addWidget
, removeWidget
/removeItem
).
您必须确保将所有小部件连接到 Qt 的 object tree,然后它将为您处理销毁。你可以通过在构造它时给它一个父对象来做到这一点。就像您对 SignalMapper
.
所做的一样
没有 "proper" 析构函数是不正确的。 是 一个析构函数 - 一个由编译器生成的析构函数 - 并且该析构函数执行释放资源所需的一切。这就是它应该的样子,这就是现代 C++ 代码应该设计的样子。
正确设计的 C++ classes 应该可以使用,而无需显式管理它们的资源。就是这样。
此外,该示例不必要地动态分配可能只是 class 成员的成员。在 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 class 中。例如,与其在析构函数中手动关闭文件句柄,不如考虑使用 QFile
,或编写类似的资源管理 class,然后您可以使用它而不必担心手动管理文件的生命周期手柄。
我在 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
类型的对象,或者如果我想自定义代码能够 remove/add 小部件。这个想法是如何删除在构造函数中创建的所有对象(动态地,使用 new
)。
我的方法是使用私有变量QList<QPushButton*> list
,将所有新分配的按钮添加到列表(在构造函数中)并在析构函数中逐个删除它们,使用上面的list
.但这似乎是幼儿园的做法。
我认为一定有其他更好的方法,无需列表,也不会弄乱构造函数代码:)感谢您的时间和耐心!
对于接收 parent 作为其构造函数参数的 Qt classes,例如 QSignalMapper,重要的是要注意 class 将自身添加到其 parent的 object 列表,当它的 parent (QObject)被销毁时,它将被销毁。
因此,如果您传递 object 和 parent,您无需执行任何操作。你的 cpp 文件中可能有一个空的析构函数,只是为了确保成员的定义对 QObject 可用,但这取决于 class.
但是,如果您确实选择编写自己的析构函数,则正确实现的典型 Qt "child" 会在销毁时从其 parent 中删除自身。
除了 caption/title 中提出的问题外,OP 询问如果他想在删除 parent 之前删除 sub-object 会发生什么:
看到 following,似乎(这是 iaw。我自己的经验)他可能只是删除了 child 小部件,并且它们会自行删除。已经提到可以将 child 的 parent 设置为空,但这不是必需的,除非有人想删除 parent 并保留 child 活着 (re-parenting...).
但是,如example最后一段所示,实例化的顺序很重要。如果 parent 在 child 之后实例化,并且 child 的 parent 被显式设置,则指向 parent 的无效引用/无效指针将是留下 child,当 child 试图从 out-of-scope parent.
中删除自身时,将在破坏 child 时发生未定义的行为除了Qt的documentation, one can also look at the implementation of QObject::setParent here (setParent_helper)。从这里可以看出,除了提到的情况外,他们付出了很大的努力来确保 children/parents 删除不会发生任何意外。
The QWidget will take ownership of layout.
来自 QLayout::addItem
(由 QLayout::addWidget
调用):
Note: The ownership of item is transferred to the layout, and it's the layout's responsibility to delete it.
您不必清理任何东西。通过布局管理小部件
(addWidget
, removeWidget
/removeItem
).
您必须确保将所有小部件连接到 Qt 的 object tree,然后它将为您处理销毁。你可以通过在构造它时给它一个父对象来做到这一点。就像您对 SignalMapper
.
没有 "proper" 析构函数是不正确的。 是 一个析构函数 - 一个由编译器生成的析构函数 - 并且该析构函数执行释放资源所需的一切。这就是它应该的样子,这就是现代 C++ 代码应该设计的样子。
正确设计的 C++ classes 应该可以使用,而无需显式管理它们的资源。就是这样。
此外,该示例不必要地动态分配可能只是 class 成员的成员。在 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 class 中。例如,与其在析构函数中手动关闭文件句柄,不如考虑使用 QFile
,或编写类似的资源管理 class,然后您可以使用它而不必担心手动管理文件的生命周期手柄。