First, hold things by value where you can. View each use of new
, make_unique
and make_shared
with suspicion - you must justify each dynamic object creation. If a sub-object has the same lifetime as the parent, holding by value is a no-brainer. For example:
class MyWidget : public QWidget {
Q_OBJECT
QGridLayout m_topLayout{this};
QLabel m_sign{"Hello World"};
public:
MyWidget(QWidget * parent = nullptr) : QWidget{parent} {
m_topLayout.addWidget(&m_sign, 0, 0);
}
};
You're passing pointers around, but object ownership is clear and there's no change of ownership. Just because a QObject
has a parent doesn't mean that the parent "owns" it. If the child is destructed before the parent, the ownership ceases. By using C++ semantics - namely the well-defined order of member construction and destruction - you have full control over child lifetimes and no QObject
parent gets to interfere.
If you have non-movable objects that have one owner, use std::unique_ptr
and move it around. That's the way to pass dynamically created QObject
s around your own code. You can remove them from the pointer at the point where you make their ownership managed by a QObject
parent, if there is such.
If you have objects with shared ownership, where their life should end as soon as possible (vs. when the application terminates, or some long-lived object gets destroyed), use std::shared_ptr
. Ensure that the pointer outlives the users. For example:
class MyData : public QAbstractItemModel { /* ... */ };
class UserWindow : public QWidget {
Q_OBJECT
std::shared_ptr<MyData> m_data; // guaranteed to outlive the view
QTreeView m_view;
public:
void setData(std::shared_ptr<MyData> && data) {
m_data = std::move(data);
m_view.setModel(m_data.data());
}
};
This example is perhaps contrived, since in Qt most users of objects watch the object's destroyed()
signal and react to the objects destruction. But this makes sense if e.g. m_view
was a third-party C API object handle that had no way of tracking the data object's lifetime.
If the object's ownership is shared across threads, then the use of std::shared_ptr
is essential: the destroyed()
signal is only usable within a single thread. By the time you get informed about object deletion in another thread, it's too late: the object has been already destroyed.
Thirdly, when you return instances of dynamically created objects from factory methods, you should return them by a naked pointer: it's clear that a factory creates an object for someone else to manage. If you need exception safety, you can return a std::unique_ptr
instead.