7

I have gone back to Qt/C++ programming recently after coding a lot with plain C++.

When browsing StackOverflow, I often catch up on posts like "Why use pointers?" where in most cases the gist of the answers is "if you can avoid it, don't use them".

When coding in C++, I now mostly try using stack variables which are passed by (const) reference or, if necessary, std::shared_ptr resp. std::unique_ptr where needed.

Getting back to Qt, I found all those "principles" to be completely ignored apparently.

I know that Qt uses its own memory management to take care of raw pointers, but here's my question:

Why don't they at least use shared_ptr or unique_ptr, particularly since they even have an own implementation QSharedPointer?

tai
  • 477
  • 1
  • 5
  • 16
  • 5
    Qt predates smart pointers. There is nothing wrong with using pointers, unmanaged use of `new` and `delete` is the problem, used correctly `QObject`s are deleted automatically by their parents – Alan Birtles Jun 10 '21 at 09:20
  • Actually, Qt can be used without `new` and `delete` although the tutorials of Qt doc. imply something else. [Example](https://stackoverflow.com/a/55889624/7478597) Before I switched to Qt, I used gtkmm where this was more usual. All these `new`s and `delete`s bothered me when I started in Qt, and I tried to stick to how I did it in gtkmm before. This works actually quite well (with some restrictions you have to have in mind). I use it this way in productive code as well. – Scheff's Cat Jun 10 '21 at 09:25
  • @Scheff'sCat delete very rarely used in QT at all because it got a form of garbage collection (i.e. elements engaged in signal-slot processing and visual elements cannot be deleted). AN owner object deletes its childrens automatically and application is owner of all GUI objects. Example like that work indeed until you would reach limit of stack size - all your objects are created on stack and have to live whole run time, so it's only remotely portable. It's more workable after 5.6 version though. – Swift - Friday Pie Jun 10 '21 at 09:41
  • @Swift-FridayPie Yes, as the Qt widgets usually take ownership for their children, you may see `new`s but no `delete`s. I completely forgot about this as I rarely use `new`. The widgets in my linked example are on stack but they might be members as well. (In this case, the resp. class will manage the life-time.) That my whole GUI has to live the whole application runtime doesn't hurt me much. a) it is needed for the whole runtime and b) this is a convention I learnt much earlier in the past for other widget sets and still am used to. – Scheff's Cat Jun 10 '21 at 09:47
  • @Scheff'sCat I've run into problem with that on Linux when combining native GUI and QtQuick, Qt 5.4 I think. Creating QtQuick engine on stack in `main()` and then exiting program was destroying inter-process pipe too early, before message to stop QtQuick engine was sent. In result program couldn't be executed second time until resident engine process was destroyed (it's a separate process). Windows follows same ideology as Qt and was always killing created process by default (as well it always stops timers and removes semaphores and shared memory objects, closes all open sockets unlike Linux). – Swift - Friday Pie Jun 10 '21 at 10:18
  • 1
    fwiw, it is raw owning pointers that are to be avoided like the plague. Raw non-owning pointers can be avoided too, but they don't suffer the same problems as raw owning pointers do. Imho the answer you link is a little misleading because it seems to put owning and non owning pointers in the same bucket – 463035818_is_not_an_ai Jun 10 '21 at 10:31
  • @463035818_is_not_a_number that's true, though in most cases, Qt included, it's hard to know if pointer is owning or not without digging into code and sometimes into framework guts. I spent a lot of time there , with different versions of Qt, sometimes having mental breakdowns when encountering code that used UB with comment "In most cases this works", because my own case wasn't "most" as I used a custom compiler and niche hardware. – Swift - Friday Pie Jun 10 '21 at 10:36
  • 1
    @Swift-FridayPie my understanding is that raw pointers nowadays should only be used non-owning. If one follows that recomendation raw pointers are fine, the hard thing is just to follow that recomendation, which is close to impossible when workign with legacy code – 463035818_is_not_an_ai Jun 10 '21 at 10:43

1 Answers1

8

Qt since versions 4.x was designed around imitating Java's framework ideology in C++ environment, using C++98 means. Instead of RAII approach of interaction it establishes "owner" - "slave" relation, in framework's term that's "parent" and "child". More of, Qt uses concept of PIMLP -private implementation. QObjects you operate with aren't real representation of what is happening, they are interfaces to completely hidden inner implementation.

By design, you have to create a QObject-derived object of child element and pass ownership to the owning object . E.g. where a window is an owner, a Button inside window will be the "slave" object. When owner is deleted, all objects that were slaved to it will be deleted too. All QObjects are thread-aware, but QWidgets can work only in main thread. This creates a non-owning pointer:

QWidget *myWidget = new QWidget(mainWindow);

mainWindow will be owning QWidget instance in this case. But this one is owning?

QWidget *myWidget = new QWidget;

It isn't. It's still owned by QApplication.

QObjectDerivedClass *myWidget = new QObjectDerivedClass;

It's an owning pointer, but this object was registered to exist in our framework. Even more, any instance can be found if it was assigned a name, storing QObjects to reach them is just an caching optimization.

All QObjects and QWidgets are registered globally and are iterable. With destruction of QApplicationCore instance all QWidgets of top level will be freed. There is undocumented exception out of that rule at least in Qt 4.x versions that QDesktopWidget objects are ignored even if they are top-level widgets. So, if a QMainWindow was forced to appear on certain screen by becoming its child, it wouldn't be destroyed.

Now comes into play signal-slot connections. In GUI certain handlers begin their work as soon as parent-child connection is established, but you can add new handlers. if a child object is deleted between beginning and end of message pump created by QEventLoop, your program may encounter an UB. To avoid it, you have to call deleteLater() which marks object for deletion at designed moment. Processing signals between threads is done separately. Practically, the main event loop is the only part of GUI that is synced with other threads.

With such complex structure and already existing requirement of working in one thread, imposed by some of supported embedded platforms, need to use smart pointers within GUi framework was negligible compared to possible impact on performance.

Before C++11 Qt had QPointer (and still got it), which is aware if QObject still exists or not using mechanics similar to owner-child interaction.

This design predates C++11 and QSharedPointer appeared only after that to fill a niche requested by users to maintain user-defined data model. It doesn't support some features, e..g you can't have an atomic version of it like ISO version does, it's only partially atomic until very last releases of 5.x. QAtomicPointer isn't either QPointer or QSharedPointer, it acts like a std::atomic<QObject*>. QSharedPointer though allows use of custom non-standalone deleter, including a call of deleteLater():

QSharedPointer<MyDataQObject> objPtr { new MyDataQObject, &QObject::deleteLater };

Assuming that MyDataQObject is derived from QObject, objPtr will call method deleteLater() in context of managed object instead of using delete on managed pointer when destroyed or reset.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42