I hope this question will find you well. I'm a little puzzled on this one. I really don't understand how Qt Object model tree handles memory with smart pointers.
I created this Qt5 basic test app just to see what will happen.
Here is the code:
child.h
#ifndef CHILD_H
#define CHILD_H
#include <QObject>
#include <QDebug>
class Child : public QObject
{
Q_OBJECT
public:
explicit Child(QObject *parent = nullptr) : QObject{parent}
{
qDebug() << "Ctor Child: " << this << ", my parent is -> " << this->parent();
}
~Child()
{
qDebug() << "Dtor Child: " << this << ", my parent is -> " << this->parent();
}
signals:
};
#endif // CHILD_H
parent.h
#ifndef PARENT_H
#define PARENT_H
#include <QObject>
#include <QWidget>
#include <QSharedPointer>
#include <QDebug>
#include "child.h"
class Parent : public QWidget
{
Q_OBJECT
public:
explicit Parent(QWidget *parent = nullptr)
: QWidget{parent}
, _child{QSharedPointer<Child>::create(this)}
{
qDebug() << "Ctor Parent: " << this << ", my parent is -> " << this->parent();
}
~ Parent()
{
qDebug() << "Dtor Parent: " << this << ", my parent is -> " << this->parent();
}
signals:
private:
QSharedPointer<Child> _child;
};
#endif // PARENT_H
main.cpp
#include "mainwindow.h"
#include "parent.h"
#include <QApplication>
#include <QList>
#include <QDebug>
static QList<QSharedPointer<Parent>> myList;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
for (int i = 1; i <= 2; ++i)
{
qDebug() << "===== Creation of Parent #" << i << "====";
QSharedPointer<Parent> p{QSharedPointer<Parent>::create()};
p->setObjectName("Parent #" + QString::number(i));
myList.append(p);
qDebug() << "=========";
}
const int value{a.exec()};
qDebug() << "value: " << value;
return value;
}
When I ran this program I got:
===== Creation of Parent # 1 ====
Ctor Child: Child(0x1118df0) , my parent is -> Parent(0x1101730)
Ctor Parent: Parent(0x1101730) , my parent is -> QObject(0x0)
=========
===== Creation of Parent # 2 ====
Ctor Child: Child(0x111d810) , my parent is -> Parent(0x11017f0)
Ctor Parent: Parent(0x11017f0) , my parent is -> QObject(0x0)
=========
value: 0
Dtor Parent: Parent(0x11017f0, name="Parent #2") , my parent is -> QObject(0x0)
Dtor Child: Child(0x111d810) , my parent is -> Parent(0x11017f0, name = "Parent #2")
Dtor Parent: Parent(0x1101730, name="Parent #1") , my parent is -> QObject(0x0)
Dtor Child: Child(0x1118df0) , my parent is -> Parent(0x1101730, name = "Parent #1")
What I don't understand is how my app is not crashing because of a double delete ? Let me explain,
When the program goes out of scope, in the process of destroying my static QList, its elements Parent QSharedPointers will be first destroyed.
Then, attempting to delete Parent will try first to delete Child. Child is a QShared pointer inside the Parent pointer. Therefore, it will be automatically delete because it is a smart pointer with a reference count going down to 0.
Then, my Parent class will be destroyed. Because Parent inherits from QWidget with inherits from QObject, Qt will apply its Object Tree destruction mechanism. Which means, Qt will delete automatically the children of Parent which should already be destroyed by the QSharedPointer above...
My question is, how come my app doesn't crash ?
I would quote my other answer C++ Qt: How widgets actually contained inside parent?, children unparent themselves when destroyed. it removes itself from the parent list of child objects.
Derived classes are destroyed before base classes, the order is as follows
~Parent
is called, destroying QSharedPointer<Child>
~Child
is calledChild::~QObject
is called, unparenting itself, parent now has no childrenParent::~QObject
is called, destroying all children, but we no longer have children! Nothing happens.Another scenario is if another QSharedPointer
exists elsewhere, delaying the child destructor, then step 2 will happen after step 4 and you would have lifetime issues, and probably crash.
Don't use QSharedPointer
for QObject
ownership if it already has a parent, instead have unique ownership and use QPointer as "observer pointer" (weak_ptr). Parents can store a raw pointer to children as a parent has longer lifetime than its children, and if thread-safety is an issue then just use signals and slots which are thread-safe, and avoid "observer pointers" which are not thread-safe.
You can use QSharedPointer
on a QObject
if it doesn't have a parent, this disqualifies QWidget
which needs to have a parent to be shown on the screen. (Excluding QWindow). an example of such object that needs to be shared is a thread-safe QObject that multiple threads interact with it and connect to it (like a thread-safe data model)