QT 上下文菜單內存泄露之QMainWindow
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 的內容介紹完了,希望本文對你有所幫助!