这段代码是否存在内存泄漏问题?

5

最终我安装了Ubuntu并设置了Qt+Valgrind以防止内存泄漏,这是在Windows中无法做到的。所以我不知道这段代码是否会导致内存泄漏?实际上,Valgrind只报告了500多个问题,但没有关于泄漏的信息。

#include <QWidget>
#include <QFrame>
#include <QVBoxLayout>
#include <QApplication>

int main(int argc, char *argv[])

{
    QApplication a(argc, argv);

    QWidget * wdgt = new QWidget;  //this line should be the cause of leakage 
                                   //if it exist (as far as i know)
    QVBoxLayout *layout = new QVBoxLayout;
    QFrame * frame = new QFrame;

    frame->setFrameStyle(QFrame::Panel | QFrame::Plain);
    frame->setLineWidth(5);
    layout->addWidget(frame);

    wdgt->setLayout(layout);
    wdgt->setFixedSize(800,600);
    wdgt->show();

    return a.exec();
}

但在这种情况下,它并不是int * a = new int; - Bob
5
每个 new 都应该有相应的 delete。或者使用智能指针。 - Ed Heal
1
@EdHeal 不适用于Qt,如果对象有父对象,则父对象会销毁它。 - Slava
@Dieter Lücking,那么Valgrind也是一个检查内存泄漏的糟糕软件吗? - Bob
2
可能是创建和释放Qt小部件对象的重复问题。 - jpo38
显示剩余7条评论
3个回答

9

请参见此帖子:创建和释放Qt小部件对象

它解释了如果一个Qt对象有一个父对象,当父对象被销毁时,该对象将自动被删除。

在你的代码中:

  • wdgtlayout的父对象,因为你执行了wdgt->setLayout(layout)
  • wdgtframe的父对象,因为你执行了layout->addWidget(frame)layout的父对象是wdgt。正如thuga所评论的那样,布局会将所有权传递给它们自己的父对象。

在你的代码中,只有wdgt是孤儿(没有Qt父对象可以自动删除它)。

要修复这个问题,你可以为其提供父对象:

QWidget * wdgt = new QWidget(&app);

因此,wdgtapp 的子级,当 app 被销毁时,它将自动被删除。

或者您可以自行删除:

int main(int argc, char *argv[])
{
    ...
    int res = a.exec();
    delete wdgt; // this will delete wdgt, but also frame and layout
    return res;
}

或者,最终,将其创建为对象,以便在超出作用域时删除:
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget wdgt;

    QVBoxLayout *layout = new QVBoxLayout;
    QFrame * frame = new QFrame;

    frame->setFrameStyle(QFrame::Panel | QFrame::Plain);
    frame->setLineWidth(5);
    layout->addWidget(frame);

    wdgt.setLayout(layout);
    wdgt.setFixedSize(800,600);
    wdgt.show();

    return a.exec();
}

顺便说一下,如果你这样写 QVBoxLayout *layout = new QVBoxLayout(wdgt),就不需要再写 wdgt->setLayout(layout)。所以这两行代码是等价的:

QVBoxLayout *layout = new QVBoxLayout(wdgt); // parenting upon construction

等同于:

QVBoxLayout *layout = new QVBoxLayout; // no parent
wdgt->setLayout( layout ); // reparenting

1
我认为最干净的方法是在堆栈上创建 QWidget wdgt - Lol4t0
实际上,layoutframe的父级都应该是wdgt。布局不会拥有小部件,它们只是将所有权传递给自己的父级。 - thuga
我认为澄清一下可能很重要,因为我看到有人删除布局,以为这样就可以摆脱布局内的小部件。这是因为他们认为这些小部件的父级是布局,销毁父级将销毁其子级。 - thuga

0

是的,您的代码存在内存泄漏问题,因为您使用了 new,但未使用Qt的内存管理。

建议使用

QApplication a(argc, argv);
QWidget * wdgt = new QWidget(&app);
QVBoxLayout *layout = new QVBoxLayout(wdgt); // optional, setLayout does that
QFrame * frame = new QFrame(layout); // optional, addWidget does that

使用Qt的内存管理。


或者您可以使用C++11共享指针:

QApplication a(argc, argv);
std::shared_ptr<QWidget> wdgt = std::make_shared<QWidget>();

QVBoxLayout *layout = new QVBoxLayout;
QFrame * frame = new QFrame;

一旦共享指针的最后一个用户超出范围,您的对象将自动删除。


3
只需要 QWidget(&app)。其他的项目都被父控件所包含(QWidget::setLayout 会使 QWidget 成为 QLayout 的父控件,而 QLayout::addWidget 则会使 QLayout 成为 QWidget 的父控件)。只有 wdgt 在原始问题中是孤立的。 - jpo38
1
当您将小部件添加到布局中时,小部件会自动重新分配父级,而当您将布局添加到小部件中时,布局也会自动重新分配。 - Lol4t0
1
此外,共享指针在这种情况下会导致二次删除(因为重新分配所有权)。 - Lol4t0
谢谢大家!这帮助了我很多。 - Bob
1
我肯定会避免将shared_ptr与Qt对象混合使用。它们是两种不同的“自动删除”机制,你很可能最终会出现双重删除的情况... - jpo38

0

你的代码存在内存泄漏问题,但首先你不应该编写需要关注资源泄漏的代码。让编译器为你处理它:

// main.cpp
#include <QtWidgets>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QWidget widget;
    QVBoxLayout layout(&widget);
    QFrame frame;

    frame.setFrameStyle(QFrame::Panel | QFrame::Plain);
    frame.setLineWidth(5);
    layout.addWidget(&frame);

    widget.setFixedSize(800,600);
    widget.show();
    return a.exec();
}

我不确定,但上面的评论假设当指针被传递时,它们被接管并且因此将被删除。如果它们在堆栈上,则不可能实现这一点。请澄清。 - Ed Heal
@EdHeal 指针被传递时,它们被接管并因此将被删除 QObject没有其子项的无限内存。当一个子项被销毁时,其父项显然不会尝试删除它。在值的销毁顺序方面,C++具有完全有用的语义。我很惊讶人们写C++代码时没有意识到这个顺序存在的充分理由,并且程序员应该利用它来获得好处。 - Kuba hasn't forgotten Monica
@Mikhail 无法同意您的观点,我已经发布了使用C++编译器和语言语义的工作正确代码。您能否详细说明您不同意的地方? - Kuba hasn't forgotten Monica
1
@Mikhail 为什么?这对任何事情都没有帮助。现代C++应该更像Python而不是C。如果你到处都看到手动内存管理,那么你做错了很多事情。 - Kuba hasn't forgotten Monica
@EdHeal 不需要。但它会不必要地动态分配微小的对象句柄。Qt广泛使用PIMPL,因此QObjectQWidget本质上都是fat指针。通过不必要地动态分配它们,你只是过早地使你的代码变慢。这是毫无意义的,当你可以直接使用值时。 - Kuba hasn't forgotten Monica
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接