c++qtpointersqpointer

How should I store pointers in Qt?


My pet project has come to the point where I should start tracking pointer lifetime, and I'm trying to develop some system for it. Sadly, the popular advice to use smart pointers everywhere does not apply since Qt API itself uses naked pointers on every occasion. So, what I came up with is this:

  1. For everything owned by Qt,

    • use pointers naked locally;
    • pass them between functions naked too;
    • store them as a subclassed QPointer that makes isNull() check before conversion to naked.
  2. For everything cleanly owned by me, use smart pointers as advised. I'm going to go with the std:: versions here.

  3. The case that bothers me. For objects that switch ownership (like widgets added/removed from a layout)

    • use, store, pass pointers naked;
    • delete them manually when appropriate.

Suggestion, comments, advice? I don't like this scheme much myself.


Solution

  • 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 QObjects 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.