QT 上下文菜单内存泄露之QMainWindow 是本人要介绍的内容,先来看内容。创建Qt工程,基于QMainwindow,什么也不做,程序会自带一个上下文菜单。
不断点击鼠标右键,菜单将反复出现,此时我用任务管理器查看其内存变化,发现每次不断增加,请问大家这是Qt的内存泄漏吗???我用MFC,CB均没有发现类此错误。
在Qt 4.7.0 和 4.7.3下可以重现该问题,在Qt 4.6.3下不存在该问题。可以确定是Qt的一个bug。
问题重现
在工具栏或停靠窗口中点击右键(弹出上下文菜单),多点击几次,然后点击按钮。观察控制台输出,可以看到很多个 QMenu 对象。
#include <QtGui>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
private slots:
void onButtonClicked();
};
MainWindow::MainWindow(QWidget *parent)
{
addToolBar("ToolBar");
addDockWidget(Qt::LeftDockWidgetArea, new QDockWidget("DockWidget"));
QPushButton * btn = new QPushButton("dump object tree");
setCentralWidget(btn);
connect(btn, SIGNAL(clicked()), SLOT(onButtonClicked()));
}
void MainWindow::onButtonClicked()
{
dumpObjectTree();
}
#include "main.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
原因
既然是QMainWindow的上下文菜单问题,直接看 contextMenuEvent 事件处理函数吧。
void QMainWindow::contextMenuEvent(QContextMenuEvent *event)
{
event->ignore();
...
QMenu *popup = createPopupMenu();
if (popup) {
if (!popup->isEmpty()) {
popup->setAttribute(Qt::WA_DeleteOnClose);
popup->popup(event->globalPos());
event->accept();
} else {
delete popup;
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
看仔细喽,这儿设置了 Qt::WA_DeleteOnClose 属性。
有什么用?设置该属性后,当我们调用该对象的 close() 成员时,隐藏(hide)窗口同时会删除(delete)该对象
有什么问题?问题出在,实际上隐藏菜单时没有 调用菜单的close(),而是 调用的hide()的成员。
调用hide()而不是close(),是的该属性不能发挥任何作用,进而导致内存泄露(Qt 之 show,hide,setVisible,setHidden,close 等小结 )。
为了对比,我们看看Qt4.6.3的源码部分:
void QMainWindow::contextMenuEvent(QContextMenuEvent *event)
{
event->ignore();
...
QMenu *popup = createPopupMenu();
if (popup && !popup->isEmpty()) {
popup->exec(event->globalPos());
event->accept();
}
delete popup;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
而这个,也就是我们的比较理想的答案了。
进一步学习
前面说了,菜单隐藏时调用的是hide() 成员,而不是close() 成员。有神马依据??
想想?如何让菜单隐藏
鼠标:点击菜单外区域
键盘:按下Esc键等
这样就比较明朗了,对吧,直接看这两个事件处理函数
键盘的按键事件(调用了hideMenu)
void QMenu::keyPressEvent(QKeyEvent *e)
{
Q_D(QMenu);
d->updateActionRects();
int key = e->key();
...
bool key_consumed = false;
switch(key) {
case Qt::Key_Escape:
key_consumed = true;
{
QPointer<QWidget> caused = d->causedPopup.widget;
d->hideMenu(this); // hide after getting causedPopup
if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
mb->d_func()->setCurrentAction(d->menuAction);
mb->d_func()->setKeyboardMode(true);
}
}
break;鼠标在菜单区域外按键,调用了hideUpToMenuBar(进而调用hideMenu)
void QMenu::mousePressEvent(QMouseEvent *e)
{
Q_D(QMenu);
...
if (!rect().contains(e->pos())) {
if (d->noReplayFor
&& QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPos()))
setAttribute(Qt::WA_NoMouseReplay);
if (d->eventLoop) // synchronous operation
d->syncAction = 0;
d->hideUpToMenuBar();
return;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
前面都调用了hideMenu,从名字也能猜猜它想干什么:
void QMenuPrivate::hideMenu(QMenu *menu, bool justRegister)
{
...
menu->hide();
}
- 1.
- 2.
- 3.
- 4.
- 5.
小结:QT 上下文菜单内存泄露之QMainWindow 的内容介绍完了,希望本文对你有所帮助!