Resource management in Qt
-
Hi all,
In modern C++, we no longer use the raw
new
operator to grasp memory from heap, but use smart pointers for that purpose, to prevent memory leak. I have doubt if Qt uses merely this way, too.We've created a custom widget like this:
Test.h
:#ifndef TEST_H #define TEST_H #include <QDialog> class QLineEdit; class QPushButton; class Test : public QDialog { Q_OBJECT public: Test(QWidget* parent = nullptr); private: QLineEdit* line; QPushButton* quit; }; #endif // TEST_H
Test.cpp
:#include <QLineEdit> #include <QPushButton> #include <QVBoxLayout> #include "test.h" Test::Test(QWidget* parent): QDialog(parent = nullptr) { line = new QLineEdit; quit = new QPushButton(tr("Quit")); connect(quit, &QPushButton::clicked, this, &Test::close); QVBoxLayout* layout = new QVBoxLayout(); layout->addWidget(line); layout->addWidget(quit); setLayout(layout); }
main.cpp
:#include<QApplication> #include "test.h" int main(int argc, char* argv[]) { QApplication app(argc, argv); Test* t = new Test; t->show(); return app.exec(); }
As you know, our custom widget is independent, because of
Test::Test(QWidget* parent): QDialog(parent = nullptr)
, and we've allocated dynamic memory for aQLineEdit
and aQPushButton
.
We've defined no destructor and even a default one is created by the compiler, it's useless here because can't release the memory grasped by rawnew
. We here have two memory leak, haven't we?In the
main.cpp
too, we've created an instance of the class bynew
. This is to me another leak.What does Qt offer for these, please?
-
You have two possibilities:
- Use the
*parent
parameter of eachQObject
to define a tree structure: https://doc.qt.io/qt-5/objecttrees.html - Use the shared/unique/... pointers from STL or Qt to manage object lifecycles. In that case, don't give a
parent
.
Regards
- Use the
-
- Use the
*parent
parameter of eachQObject
to define a tree structure: https://doc.qt.io/qt-5/objecttrees.html
So if we use the constructor the following way, the widget's still dependent on its parent (QDialog):
public: Test(); ...
And it's destroyed, and all resources are released, when the program closes. But that mightn't be proper always!
For example, consider a program running for 5 hours. In its first hour, it creates a number of objects using new (from heap), then goes for other sections and leaves them out, useless for the rest (four hours). Memory leak!
- Use the shared/unique/... pointers from STL or Qt to manage object lifecycles. In that case, don't give a
parent
.
Now, to prevent the leaks above, if we give a
parent = nullptr
, to use a unique pointer from STL:std::unique_ptr<QPushButton> quit(new QPushButton);
I get errors!
I also think use of smart pointers the way it's done in std C++ is simpler than the way it's handled by Qt.
- Use the
-
@tomy said in Resource management in Qt:
For example, consider a program running for 5 hours. In its first hour, it creates a number of objects using new (from heap), then goes for other sections and leaves them out, useless for the rest (four hours). Memory leak!
If you have a widget that is being displayed (or not, when you call
setVisible(false)
on it), it is used memory. It does not matter if the user of your program clicks on anything or not, and it does not matter how long the application is open. It is not a memory leak.However, if you have a widget and close it, but you don't free the memory because you set the parent-child architecture wrongly - then it is a memory leak indeed, but it's your bug, not Qt fault.
std::unique_ptr<QPushButton> quit(new QPushButton);
It helps when you write correct code ;-)
auto quit = std::make_unique<QPushButton>();
-
Hi
Something like
std::unique_ptr<QPushButton> quit=make_unique<QPushButton>();
But there is one cavecat.
If you use an std::unique_ptr, for your widget, you cannot insert into anything.
Since if assign a parent, you get double delete.Having each widget in its own std::unique_ptr would be overkill.
Qt current system where one parent/owner delete all sub children makes a cleaner syntax and far less fuss using the children.QDialog have an option for
diag->setAttribute(Qt::WA_DeleteOnClose);
Which means as soon as the user closes it, it will deallocate itself and all children.
So it's possible to tweak how fast you want memory back. -
I'm confused! Seemingly that parent-child relationship of Qt is solely for collecting memory leaks. Well, good, if so. Then how does Qt this way want to follow C++'s progress, for instance those smart pointers!?
Using merely raw memory allocation, using
new
, and relying on Qt's parent-child relationship and wanting no to have any memory leak is error-prone and difficult to cope with, properly completely. Smart pointers are the remedy.By the way, I get these errors. Quite possibly I need to include some library.
-
@tomy said in Resource management in Qt:
Seemingly that parent-child relationship of Qt is solely for collecting memory leaks. Well, good, if so.
What? No, meory leak prevention is not the main objective of this system. Parent-child trees also help widgets properly paint (within other widgets - to build complex UIs).
Then how does Qt this way want to follow C++'s progress, for instance those smart pointers!?
You can join that discussion on Qt development mailing list, Qt 6 is being discussed as we speak, and smart pointers are part of that discussion as well (as is the future of Qt smart pointers: QPointer, QSharedPointer, QWeakPointer etc.)
Using merely raw memory allocation, using new, and relying on Qt's parent-child relationship and wanting no to have any memory leak is error-prone and difficult to cope with, properly completely. Smart pointers are the remedy.
Have you ever actually used
std::unique_ptr
? While it is indeed a great thing, but also "difficult to cope with" describes it neatly :-) in my opinion.Using QPointer and parent-child for UI elements is very easy and perfectly safe from leaks. In all other parts of apps - indeed, smart pointers should be used. There is nothing wrong in using best thing from both worlds, in one app :-) Anyway, if you have a good suggestion how to change Qt to use std smart pointers without breaking thousands of applications using Qt already, people on the mailing list will be glad to hear it.
-
@tomy said in Resource management in Qt:
In modern C++, we no longer use the raw new operator to grasp memory from heap, but use smart pointers for that purpose, to prevent memory leak.
That's an oversimplification. Imagine you have a generic node graph. You have a std::shared_ptr to a Graph. The Graph has a vector of std::shared_ptr's to Nodes, and Nodes can have std::shared_ptrs to each other.
When you are done with a graph, you make the shared_ptr go out of scope, and the Graph's destructor executes. It ends the lifetime for the vector of shared_ptr's to the nodes, and they all have their ref counts decremented. But, many of the Nodes themselves have shared_ptrs to each other, so it doesn't decrement the ref count to 0.
Now, you have a web of Node objects in memory that haven't been destructed or their memory reclaimed, but you have now way to reach any of them! Oh no, smart pointers didn't save us, and we still have a memory leak! We still needed to think through an owning relationship (Like calling the graph the "parent" of the Nodes, and making it responsible for freeing the Nodes regardless of refcounts...) to be certain of what happens.
Smart pointers are a tool. A useful tool. But not magic, and they don't relieve you from having to think about ownership and responsibilities when it comes to memory allocation and management. And smart pointers don't make "new" a dirty word. You just have to understand how the API's you are using work, and use the right tool for the right job.
-
We had a discussion about it some 3 years ago.
The gist of it is anything that takes ownership of some resource and releases it is a type of smart pointer. It doesn't have to have "smart" or "pointer" in the name. Having said that there's not much difference between this:
auto owner = std::make_unique<QPushButton>();
and this:
new QPushButton(parent);
In the first case owner deletes the button when it is deleted itself. In the second case same thing happens - button is deleted by the parent when it is deleted itself. From memory management perspective that's exactly the same thing. QObjects are sort of a smart pointers for their children.
-
@wrosecrans said in Resource management in Qt:
Smart pointers are a tool. A useful tool.
A tool that, sadly, many people, based on the recommendations of few people, have decided is the ultimate way to manage memory. And to make it worse they're willing to jump hoops to make it work, even if not warranted (neither the first time, nor the last - yes, singletons are such a case). Clear ownership is always going to rule supreme; no smart pointer (which isn't even a pointer[1]) can substitute.
[1]: We call stack objects that manage a pointer "smart pointers", but in fact they're not. They are objects, and they are allocated with automatic storage duration.
But not magic, and they don't relieve you from having to think about ownership and responsibilities when it comes to memory allocation and management. And smart pointers don't make "new" a dirty word. You just have to understand how the API's you are using work, and use the right tool for the right job.
Amen brother!
-
meory leak prevention is not the main objective of this system
Up to now, I don't think Qt's parent-child mechanism is completely successful in that objective, because the resources are mainly freed when the project closes. While modern OSes do the same task after closing the program, making Qt's work actually useless in effect.
QPointer also is apparently successful only in preventing dangling pointers and it is different from memory leak.
Have you ever actually used std::unique_ptr?
No, not yet. How about you? ;)
Let me ask you this question,please. How best do you write a parented project like this:
test.h:
#include <QDialog> class QLineEdit; class QSpinBox; class QFormLayout; class Test : public QDialog { Q_OBJECT public: Test(QWidget* parent = nullptr); void do_something_else(); private: QFormLayout* formLayout; QLineEdit* nameLineEdit; QSpinBox* ageSpinBox; };
test.cpp:
Test::Test(QWidget* parent): QDialog(parent) { formLayout = new QFormLayout; nameLineEdit = new QLineEdit; ageSpinBox = new QSpinBox; formLayout->addRow(tr("&Name:"), nameLineEdit); formLayout->addRow(tr("&Age:"), ageSpinBox); setLayout(formLayout); do_something_else(); } void Test::do_something_else() { // Accomplish some other task for several hours }
-
@tomy said in Resource management in Qt:
Up to now, I don't think Qt's parent-child mechanism is completely successful in that objective, because the resources are mainly freed when the project closes.
That's not at all true. The resources are released whenever you release them. It has nothing to do with smart pointers or parent child relation. There's no automatic garbage collection or anything like that. As was said above, you as a designer decide when objects are created and destroyed. If you don't want an object to exist just delete it. Qt doesn't dictate whether you create an object on a stack or a heap. You can delete it whenever you see fit. The parent child relation just lets you conveniently delete the top level object and it will clean all of its children, but you can manually delete them at any point before that if you want.
As to your example - that's just poorly designed code. Nothing to do with Qt. If you want a long lasting task to run and you don't want the dialog to stick around then structure your program like that. Certainly don't run tasks lasting several hours in a blocking call in ui thread. That's just bad.
You can do it like this for example:
class Whatever { TaskSettings get_task_settings() { SomeSettingsDialog dialog; dialog.exec(); return dialog.settings(); } void do_something_else { TaskSettings settings = get_task_settings(); StartTaskInAnotherThread(settings); //and exit immediately } }
Nothing is leaked, no dialog resources are held longer than needed, no smart pointers or otherwise.
-
You radically changed my code and now neither I get any new thing useful from that nor is the ambiguity of that relation more eliminated. I was reading your post of three years ago and was finding it useful.
Just one point is clear to me for this post: smart pointers are created for occasions we "forget" to "delete" a memory block allocated dynamically. If we can write delete whenever needed, so why are modern pointers called "smart"?
auto owner = std::make_unique<QPushButton>();
This since isn't a static member doesn't work either on my Qt Creator!
error: 'auto' not allowed in non-static class member -
@tomy said in Resource management in Qt:
I don't think Qt's parent-child mechanism is completely successful in that objective, because the resources are mainly freed when the project closes. While modern OSes do the same task after closing the program, making Qt's work actually useless in effect.
As stated before - that depends solely on how you build your UI. If you have a GUI app - let's say a web browser - then obviously the UI needs to stay in memory while the application is open. Otherwise users won't see anything... and these resources need to be freed only when the whole application is being closed. There is nothing that smart pointers would change in this scenario.
There are parts of that app, though, that do benefit from more smartness - browser tabs. Each new tab allocates memory, and closing the tab should free that memory. With Qt's approach (parent-child), you don't have any memory leaks here: a tab is created, added to the tree. You can delete it when the tab is closed, but if you forget to do it in code, no memory will leak - Qt will "remember" to free it when application is closed. Using smart pointers here would give you exactly the same result. The programmer still needs to remember to delete/free the memory when necessary for "performance" reasons like that.
Let me ask you this question,please. How best do you write a parented project like this:
What you wrote seems fine. Whether you run some task for several hours or for 1 minute does not matter at all from memory perspective.
-
You can delete it when the tab is closed, but if you forget to do it in code, no memory will leak - Qt will "remember" to free it when application is closed
This is exactly the point I'm referring to. If the user continually opens (creates, dynamically) new tabs during work and closes them forgetting to set "delete" in code, without closing the main window of the browser, Qt releases those tabs' reserved memory beforehand, at the time of closing the app. Right, up to here?
I say it is useless, because most modern OSes do this task exactly when a program closes.
also you say:
Using smart pointers here would give you exactly the same result. The programmer still needs to remember to delete/free the memory when necessary
I disagree with this. Smart pointers have a "delete" in their destructors, so it's guaranteed that when they go out of scope, (just like the new tabs when they are closing), their resources using the destructors of those smart pointers are freed while the app is still running.
With what part of my opinions above do you disagree, please?
-
@tomy said in Resource management in Qt:
Just one point is clear to me for this post: smart pointers are created for occasions we "forget" to "delete" a memory block allocated dynamically. If we can write delete whenever needed, so why are modern pointers called "smart"?
No they are not. They are created for specific circumstances where they're useful. The thing a scoped pointer is useful for is exception-safety. Shared data pointers (Qt) are created for COW (allowing shallow copies) and thus reentrancy. Shared pointers are for when the objects manage their own lifetimes - they're their own owner so to speak. If you don't allow exceptions then stack unwinding a scoped pointer gives you nothing. If your object is not managing its own lifetime then using a shared pointer means you're trying to fit a square peg in a round hole.
This is exactly the point I'm referring to. If the user continually opens (creates, dynamically) new tabs during work and closes them forgetting to set "delete" in code, without closing the main window of the browser, Qt releases those tabs' reserved memory beforehand, at the time of closing the app. Right, up to here?
If they're parented to something, then they're freed when the something is destroyed. It's not Qt's job (nor the OS's for that matter) to clean up your room, you know, is what we're saying. You need to tidy up, that's your responsibility as a programmer. Forgetting to delete what you acquired is a bug in your code, not in the library.
I disagree with this. Smart pointers have a "delete" in their destructors, so it's guaranteed that when they go out of scope, (just like the new tabs when they are closing), their resources using the destructors of those smart pointers are freed while the app is still running.
Same goes for the parent-child relationship. When the parent object goes out of scope (i.e. is deleted) then it's going to free its children, not before that, not at some unknown point in time.
With what part of my opinions above do you disagree, please?
I disagree with two things:
- You're not realizing a smart pointer is not a pointer but an object. The stack frees the object then the object is going to delete the data block.
- This is no different from what the parent does.
-
They are created for specific circumstances where they're useful.
What circumstances, only exceptions?
So you agree that, when each new tab in the browser example is created using a smart pointer, its resource will be freed when it closes, not necessarily at the time the user closes the whole app.
You're not realizing a smart pointer is not a pointer but an object.
I know that. Once again in the above example, when a new tab closes, that "object" goes out of scope handling the destructor of its class to run freeing the newly closed tab's resource.
So if this scenario is done properly with smart pointers, and you say the same goes with the parent-child relationship if they're parented to something, OK. What is that thing, in our example? The main window of the browser?
-
@tomy said in Resource management in Qt:
What circumstances, only exceptions?
Only? That's enough not to leak memory all over the place.
So you agree that, when each new tab in the browser example is created using a smart pointer, its resource will be freed when it closes, not necessarily at the time the user closes the whole app.
No, I don't.
Once again in the above example, when a new tab closes, that "object" goes out of scope handling the destructor of its class to run freeing the newly closed tab's resource.
There's no such example that I could see.
So if this scenario is done properly with smart pointers, and you say the same goes with the parent-child relationship
It does.
if they're parented to something, OK. What is that thing, in our example? The main window of the browser?
Usually (actually almost always) you parent widgets to their visual parent. For a tab that could be a dialog or a main window. For a dialog it can be another dialog or a main window, etc. None of the smart pointers can know if you want to free the object when you close the tab or not, so that's up to you. You can for example want the tab to persist over some time, even if not visible. And I've done this, whenever I have a widget that's heavy to populate for w/e reason. On the other hand when the dialog's deleted its going to free all it's child widgets (provided they're parented), so I really don't see why you'd want to even use a smart pointer in that case. Moreover I'd rather create the widgets on the stack, where applicable, then the language is freeing them for me, no need to call
new
, nordelete
. -
Thank you.
The issue still is not solved for me, but I would rather not continue this thread because it's just this way being stretched out more and more.Probably, in the future, or when studying other resources, I can comprehend the matter.
-
@tomy Just my 2 cts about parent relationship of QtObject. This has nothing to do with smartpointer philosophy, the point is to ensure than every child object will be distroyed when the parent object is distroyed.
So combining smartpointers and parent-child relationship is a very bad idea, because smartpointer are there to clear memory when all copies of the pointer are distroyed... So you will have double memory release ==> crash!
But Qt also includes another type of smartpointer calledQPointer
, which will becomes null when the QObject is deleted, and distroying the the QPointer, will notThe other advantage of parent-child relationship, is that parent and childs always living in the same thread, moving parent object to another will also move all child to the same thread. Moving childs to another thread is not allowed.
There is only 1 rule about parent-child relationship and smartpointer, avoid to combine them.