Consider this use case: When a QObject's lifetime is managed elsewhere, e.g. by the C++ scoped lifetime (as a local variable, or as a class member, etc.), or with a shared pointer, its parent should not attempt to delete it in the ~QObject() destructor. Is there a way to truly pass the ownership of the object to the shared pointer, so that the parent won't attempt to delete it?
What may be the reasons for setting a parent if we don't intend ownership? That they exist follows from the fact that the parent-child relationship is appropriated for multiple purposes in Qt, and there's no built-in way to decouple them:
[GC] The parent acts as a garbage collector for the children objects: if any survive until parent's destruction, the parent will destroy and deallocate them.
[Thread] The child's thread affinity follows that of the parent.
This was added to Qt 4 when QObject began supporting multi-threaded operation and gained the thread property.
[WidgetTree] Widgets use the parent-child relationship as an edge in the widget tree.
Qt::Window flag - then it is a top-level widget, and is a root of its own widget tree, but is still garbage-collected by the parent.The immediate goal is to decouple the [GC] from other functionalities, and allow it to be disabled per-object. The extended goal is to decouple all three functionalities from each other.
We can intercept the parent's ~QObject() destructor and remove the object from the child list before the child objects in that list get deleted. Luckily, ~QObject emits the destroyed signal before deleting its children.
Thus, to change an object's parent to a non-owning parent, we must take action only when it matters: when the parents' destructor gets invoked. Thus, we
Intercept parent's destroyed signal and clear the object's parent.
Intercept the parent losing the child, or, if the parent doesn't receive such events, intercept the events sent to the child and use them as a blunt hook to detect if the parent got changed.
This can be implemented in a free-standing utility function and a helper class, without the need to modify the code of any of the objects involved.
// https://github.com/KubaO/stackoverflown/tree/master/questions/qobject-sep-concerns-55046944
#include <QtWidgets>
class ParentTracker : public QObject {
QMetaObject::Connection connection;
QObject *subjectParent = nullptr;
inline QObject *subject() const { return parent(); }
bool eventFilter(QObject *receiver, QEvent *event) override {
qDebug() << receiver << event->type();
if (receiver == subject()) {
// Track parent changes on the child
if (subject()->parent() != subjectParent) {
detachFromParent();
attachToParent();
}
} else if (event->type() == QEvent::ChildRemoved) {
// Track child changes on the parent
Q_ASSERT(receiver == subjectParent);
auto *ev = static_cast<QChildEvent *>(event);
if (ev->child() == subject()) {
detachFromParent();
}
}
return false;
}
void lostParent() {
subject()->setParent(nullptr);
detachFromParent();
}
void detachFromParent() {
if (subjectParent) {
disconnect(connection);
connection = {}; // free the connection handle immediately
subjectParent->removeEventFilter(this);
subjectParent = nullptr;
}
}
void attachToParent() {
Q_ASSERT(!subjectParent);
subjectParent = subject()->parent();
bool snoopChild = !subjectParent;
{
auto *widget = qobject_cast<QWidget *>(subject());
snoopChild = snoopChild ||
(widget && widget->testAttribute(Qt::WA_NoChildEventsForParent));
}
if (subjectParent) {
auto *widget = qobject_cast<QWidget *>(subjectParent);
snoopChild = snoopChild ||
(widget && widget->testAttribute(Qt::WA_NoChildEventsFromChildren));
connection = connect(subjectParent, &QObject::destroyed, this,
&ParentTracker::lostParent);
}
if (snoopChild)
subject()->installEventFilter(this);
else {
Q_ASSERT(subjectParent);
subject()->removeEventFilter(this);
subjectParent->installEventFilter(this);
}
}
public:
explicit ParentTracker(QObject *child) : QObject(child) {
Q_ASSERT(subject());
attachToParent();
}
};
ParentTracker *detachQObjectOwnership(QObject *child) {
Q_ASSERT(child && (!child->thread() || child->thread() == QThread::currentThread()));
QObject *parent = child->parent();
if (!parent) return nullptr;
if (parent->thread() != child->thread()) return nullptr;
return new ParentTracker(child);
}
template <class T> void setup(QPointer<QObject> &parent, QPointer<QObject> &child) {
parent = new T;
child = new T(static_cast<T*>(parent.data()));
parent->setObjectName("parent");
child->setObjectName("child");
Q_ASSERT(parent && child);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QPointer<QObject> parent, child, tracker;
// parent-child ownership
setup<QObject>(parent, child);
delete parent;
Q_ASSERT(!parent && !child);
// parent-child without ownership
setup<QObject>(parent, child);
tracker = detachQObjectOwnership(child);
delete parent;
Q_ASSERT(!parent && child && tracker);
delete child;
Q_ASSERT(!parent && !child && !tracker);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With