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();
- }
原因
既然是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;
- }
- }
- }
看仔细喽,这儿设置了 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;
- }
而这个,也就是我们的比较理想的答案了。
进一步学习
前面说了,菜单隐藏时调用的是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;
- }
- }
前面都调用了hideMenu,从名字也能猜猜它想干什么:
- void QMenuPrivate::hideMenu(QMenu *menu, bool justRegister)
- {
- ...
- menu->hide();
- }
小结:QT 上下文菜单内存泄露之QMainWindow 的内容介绍完了,希望本文对你有所帮助!