QObject Lifetime & Ownership Guidelines
Overview
Qt’s object model is built around heap allocation + parent–child ownership. However, ManiVault’s codebase deliberately uses a different pattern in many places:
Most
QObject-derived helper objects are stored as stack/member subobjects, while only top-level components are dynamically allocated.
This document explains: - When this pattern is safe - When it is unsafe - Why ManiVault uses it - The constraints developers must follow
Qt’s Default Ownership Model
In standard Qt practice:
auto *child = new QObject(parent);
Parent owns the child.
Parent deletes child in
QObject::~QObject().This model assumes heap allocation.
This is the most common and safest approach when: - Objects are reparented - Ownership is transferred - Objects are shared across components - Objects are passed to Qt containers (menus, widgets, toolbars, etc.)
ManiVault’s Allocation Strategy
Design Principle
In ManiVault:
Top-level components are heap allocated.
Internal helper QObjects are often stack/member objects.
Parent is typically set to
this.Ownership is strictly hierarchical and local.
Example:
class StandardItemModel : public QStandardItemModel {
Q_OBJECT
private:
gui::NumberOfRowsAction _numberOfRowsAction{this};
};
Why We Do This
The reasons are architectural and deliberate:
Deterministic lifetime
Member subobjects are destroyed automatically.
No raw
new/deletemanagement.
No scattered heap allocations
Improves locality.
Reduces pointer chasing.
Easier reasoning about ownership.
Strict containment
Helpers never outlive their owner.
Ownership boundaries are explicit in class structure.
Architectural clarity
Object graph mirrors class composition.
No hidden ownership transfers.
This approach works because the majority of these objects: - Are not reparented - Are not handed to ownership-taking APIs - Do not cross thread boundaries - Are fully contained within their owning component
When Stack-Allocated QObjects Are Safe
A QObject-derived member object is safe only if all of the following
are true:
✔ Parent is either: - this (the enclosing owner), OR - nullptr
permanently
✔ It is never reparented
✔ It is never passed to APIs that assume ownership
Examples of risky APIs: - QWidget::addAction() -
QMenu::addAction() - QToolBar::addAction() - Any API that may call
setParent()
✔ It is never deleted via deleteLater()
✔ It does not receive queued signals after derived destruction begins
Common Failure Mode (Linux-only Crashes)
Typical assertion:
ASSERT failure in qobjectdefs_impl.h:
"Called object is not of the correct type
(class destructor may have already run)"
This usually indicates:
A queued signal is invoking a slot in a derived class after that derived destructor has already executed.
This is not inherently a stack-allocation issue.
It is usually caused by:
Qt::QueuedConnectionCross-thread
AutoConnectionTimers still running during teardown
Signals emitted during destruction
Linux timing makes these more visible than Windows.
Required Teardown Discipline
When using stack/member QObjects:
Always stop active emitters in destructor
~ActionsHierarchyModel() override
{
_timer.stop();
QObject::disconnect(&_numberOfRowsAction, nullptr, this, nullptr);
}
Avoid queued connections into derived classes during teardown
If necessary, disconnect in the derived destructor.
When Heap Allocation Is Required
Use heap allocation (new + parent) if any of the following apply:
Object may be reparented
Object may outlive the owning component
Object participates in UI container ownership
Object crosses thread boundaries
Object may receive queued events late in destruction
Object is part of a plugin boundary
Ownership is not strictly hierarchical
Example:
_numberOfRowsAction = new gui::NumberOfRowsAction(this);
This eliminates: - Accidental ownership transfer - Undefined delete behavior - Destructor ordering hazards
Project Policy
We do NOT mandate heap allocation for all QObjects.
Instead:
Stack/member QObjects are allowed and widely used.
They must follow the safety constraints defined above.
High-risk categories should prefer heap allocation.
High-risk categories
Prefer heap allocation for:
QActionQTimerUI-related QObjects
Cross-thread workers
Plugin-boundary objects
Decision Rule for Developers
Before adding a stack-allocated QObject member, ask:
“Could any other object ever delete or reparent this QObject?”
If the answer is yes or maybe, allocate it on the heap.
If the answer is definitively no, stack allocation is acceptable.
Summary
ManiVault’s stack-allocation pattern:
Is intentional.
Improves architectural clarity and determinism.
Works reliably under strict constraints.
Requires disciplined ownership and teardown management.
We do not refactor the entire codebase to heap allocation.
Instead, we:
Maintain strict containment rules.
Convert only high-risk objects to heap allocation.
Ensure teardown is safe against queued delivery.