Pass parent to non-pointer `QObject`-derived members?
-
I have been looking at an old code base that uses Qt and noticed something that seems a bit odd.
Probably easier to illustrate in code than explain it:
class MyApp: public QObject { Q_OBJECT ... private: // data members A m_a; // <<< NOTE not dynamically allocated ... }; ... // implementation MyApp::MyApp() : QObject(), m_a(this), m_b(this) // <<< NOTE parent passed in { ...Here,
A,Betc. are themselvesQObject-derived classes. e.g.:class A : public QObject { Q_OBJECT public: A(QObject* parent=nullptr); ...The thing that surprises me here is that the
m_aandm_bmembers will be deleted anyway whenMyAppis destroyed, but by passingthisinto their constructors, the parent-child relationship is set up and that could lead to a double deletion.I am aware that there are situations where one can create
QObject-based types on the stack and pass in a parent, but that one has to be very careful about lifetimes to avoid the sort of problem I just mentioned.Am I correct to be concerned here or am I missing something? This is an old code base that seems to be working as far as I know but the original author is no longer around to ask about it.
-
I have been looking at an old code base that uses Qt and noticed something that seems a bit odd.
Probably easier to illustrate in code than explain it:
class MyApp: public QObject { Q_OBJECT ... private: // data members A m_a; // <<< NOTE not dynamically allocated ... }; ... // implementation MyApp::MyApp() : QObject(), m_a(this), m_b(this) // <<< NOTE parent passed in { ...Here,
A,Betc. are themselvesQObject-derived classes. e.g.:class A : public QObject { Q_OBJECT public: A(QObject* parent=nullptr); ...The thing that surprises me here is that the
m_aandm_bmembers will be deleted anyway whenMyAppis destroyed, but by passingthisinto their constructors, the parent-child relationship is set up and that could lead to a double deletion.I am aware that there are situations where one can create
QObject-based types on the stack and pass in a parent, but that one has to be very careful about lifetimes to avoid the sort of problem I just mentioned.Am I correct to be concerned here or am I missing something? This is an old code base that seems to be working as far as I know but the original author is no longer around to ask about it.
@Bob64 said in Pass parent to non-pointer `QObject`-derived members?:
the parent-child relationship is set up and that could lead to a double deletion.
There can be no doulbe deletion in your case
in the dtor first the members of a class get destroyed before the dtor of the base class is called. Therefore m_a and m_b are already destroyed (and therefore removed from the parent's children list) before the parent cleans up it's children.
-
I have been looking at an old code base that uses Qt and noticed something that seems a bit odd.
Probably easier to illustrate in code than explain it:
class MyApp: public QObject { Q_OBJECT ... private: // data members A m_a; // <<< NOTE not dynamically allocated ... }; ... // implementation MyApp::MyApp() : QObject(), m_a(this), m_b(this) // <<< NOTE parent passed in { ...Here,
A,Betc. are themselvesQObject-derived classes. e.g.:class A : public QObject { Q_OBJECT public: A(QObject* parent=nullptr); ...The thing that surprises me here is that the
m_aandm_bmembers will be deleted anyway whenMyAppis destroyed, but by passingthisinto their constructors, the parent-child relationship is set up and that could lead to a double deletion.I am aware that there are situations where one can create
QObject-based types on the stack and pass in a parent, but that one has to be very careful about lifetimes to avoid the sort of problem I just mentioned.Am I correct to be concerned here or am I missing something? This is an old code base that seems to be working as far as I know but the original author is no longer around to ask about it.
@Bob64 that should be fine and in certain contexts it's actually a good idea to give parents to objects allocated on the stack.
see this SO answer for a really good explanation
https://stackoverflow.com/a/37375210/15422846 -
I have been looking at an old code base that uses Qt and noticed something that seems a bit odd.
Probably easier to illustrate in code than explain it:
class MyApp: public QObject { Q_OBJECT ... private: // data members A m_a; // <<< NOTE not dynamically allocated ... }; ... // implementation MyApp::MyApp() : QObject(), m_a(this), m_b(this) // <<< NOTE parent passed in { ...Here,
A,Betc. are themselvesQObject-derived classes. e.g.:class A : public QObject { Q_OBJECT public: A(QObject* parent=nullptr); ...The thing that surprises me here is that the
m_aandm_bmembers will be deleted anyway whenMyAppis destroyed, but by passingthisinto their constructors, the parent-child relationship is set up and that could lead to a double deletion.I am aware that there are situations where one can create
QObject-based types on the stack and pass in a parent, but that one has to be very careful about lifetimes to avoid the sort of problem I just mentioned.Am I correct to be concerned here or am I missing something? This is an old code base that seems to be working as far as I know but the original author is no longer around to ask about it.
@Bob64 said in Pass parent to non-pointer `QObject`-derived members?:
the parent-child relationship is set up and that could lead to a double deletion.
There can be no doulbe deletion in your case
in the dtor first the members of a class get destroyed before the dtor of the base class is called. Therefore m_a and m_b are already destroyed (and therefore removed from the parent's children list) before the parent cleans up it's children.
-
@Bob64 that should be fine and in certain contexts it's actually a good idea to give parents to objects allocated on the stack.
see this SO answer for a really good explanation
https://stackoverflow.com/a/37375210/15422846@J-Hilk , @Bob64
So let's go through this actual example, in the light of the referenced explanation/solution.Remember that there the poster says:
QObject *parent = new QObject; QObject child(parent); delete parent;is the bad case, claiming that
delete parentwill attempt todelete child, with obvious consequences.MyApp::MyApp() : QObject(), m_a(this), m_b(this) // <<< NOTE parent passed inThe outside world initially goes
app = new MyApp. Then later, say it goesdelete app. When exactly do member variables get destroyed in this scenario? Because if it does not "detach"m_a, m_bfromthisbefore the destructor ofthisgets to seem_a, m_bhavethisas their parent, we will be in the original bad situation.I guess the
m_a, m_bvaribales get detached/destroyed before the class they are in gets to run its destructor code, which woulddeletethem? Which makes sense, but makes you think....If this were not so I imagine the existing code @Bob64 says is working would have fallen foul of it.
EDIT
While I was typing this @Christian-Ehrlicher has postedin the dtor first the members of a class get destroyed before the dtor of the base class is called. Therefore m_a and m_b are already destroyed (and therefore removed from the parent's children list) before the parent cleans up it's children.
which would make sense, as suspected, and not cause a problem.
So a C++ destructor does not get called till after its member variables have been removed? But then the destructor could not reference its member variables. I'm confused in this area.....
before the dtor of the base class is called
Ah! That makes sense. Calling
deleteon children only happens in the base destructor, and by thenm_a, m_bhave been removed from the derived class.... -
@J-Hilk , @Bob64
So let's go through this actual example, in the light of the referenced explanation/solution.Remember that there the poster says:
QObject *parent = new QObject; QObject child(parent); delete parent;is the bad case, claiming that
delete parentwill attempt todelete child, with obvious consequences.MyApp::MyApp() : QObject(), m_a(this), m_b(this) // <<< NOTE parent passed inThe outside world initially goes
app = new MyApp. Then later, say it goesdelete app. When exactly do member variables get destroyed in this scenario? Because if it does not "detach"m_a, m_bfromthisbefore the destructor ofthisgets to seem_a, m_bhavethisas their parent, we will be in the original bad situation.I guess the
m_a, m_bvaribales get detached/destroyed before the class they are in gets to run its destructor code, which woulddeletethem? Which makes sense, but makes you think....If this were not so I imagine the existing code @Bob64 says is working would have fallen foul of it.
EDIT
While I was typing this @Christian-Ehrlicher has postedin the dtor first the members of a class get destroyed before the dtor of the base class is called. Therefore m_a and m_b are already destroyed (and therefore removed from the parent's children list) before the parent cleans up it's children.
which would make sense, as suspected, and not cause a problem.
So a C++ destructor does not get called till after its member variables have been removed? But then the destructor could not reference its member variables. I'm confused in this area.....
before the dtor of the base class is called
Ah! That makes sense. Calling
deleteon children only happens in the base destructor, and by thenm_a, m_bhave been removed from the derived class....@JonB said in Pass parent to non-pointer `QObject`-derived members?:
So a C++ destructor does not get called till after its member variables have been removed? But then the destructor could not reference its member variables. I'm confused in this area.....
You misunderstood me. The dtor of MyApp gets called, cleans up it's stuff (incl. m_a and m_b) and then the base dtor is called (= QObject's dtor) which cleans up it's children (qDeleteAll(m_children); - not that easy but enough for here). But since m_a and m_b were already cleaned up (and therefore removed from the QObject's children list) all is fine.
hat makes sense. Calling delete on children only happens in the base destructor, and by then m_a, m_b have been removed from the derived class....
Yey
-
@Christian-Ehrlicher 's explanation in particular helped me nail what was missing from my understanding but thanks all for your replies. It was all useful discussion.