QMainWindow上下文菜单内存泄露(QTBUG)

晨曦之光 发布于 2012/05/08 10:16
阅读 314
收藏 0
Qt

起源

CSDN论坛有网友抱怨:

创建Qt工程,基于QMainwindow,什么也不做,程序会自带一个上下文菜单。
不断点击鼠标右键,菜单将反复出现,此时我用任务管理器查看其内存变化,发现每次不断增加,请问大家这是Qt的内存泄漏吗???我用MFC,CB均没有发现类此错误。

在Qt4.7.0 和 4.7.3下可以重现该问题,在Qt4.6.3下不存在该问题。可以确定是Qt的一个bug。

  • 惭愧 :还没学会怎么提交bug、提交patch、然后找人来审核。先记录下来,晚些时候学习好好学习一下这些流程然后再提交

问题重现

在工具栏或停靠窗口中点击右键(弹出上下文菜单),多点击几次,然后点击按钮。观察控制台输出,可以看到很多个 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();
}

原文链接:http://blog.csdn.net/dbzhang800/article/details/6422188
加载中
返回顶部
顶部