Home > OS >  why qt application always new widgets instead of on stack?
why qt application always new widgets instead of on stack?

Time:02-12

there. I've seen this example from qt: https://doc.qt.io/qt-5/qtwidgets-widgets-calculator-example.html, and wonder why qt always create widgets with new instead of allocating on stack

Button *Calculator::createButton(const QString &text, const char *member)
{
    Button *button = new Button(text);
    connect(button, SIGNAL(clicked()), this, member);
    return button;
}

create widget on stack is faster, and in this question: QT-specific difference of stack vs. heap attributes? Mike's answer says that create on stack is perfectly fine, but why official documents use new mostly?

CodePudding user response:

This is simple: because you want the widget to live even after this function scope is finished and QObjects are not copyable. The following code does not work because you cannot copy the object.

Button Calculator::createButton(const QString &text)
{
    Button button(text); // allocated on stack, it is OK so far...
    return button; // nope, you cannot copy that! it does not compile.
}

The following code does not work either because you would be passing a deleted object.

Button *Calculator::createButton(const QString &text)
{
  Button button(text); // allocated on stack, it is OK so far...
  return &button; // ... returning pointer, still OK...
  // Oh no! End of scope here, the object gets murdered now. 
  // whoever holds the pointer to it, holds a dead, invalid object. 
}

The object gets deleted at the end of scope and you would be passing a pointer to freed, hence invalid memory.

The example you referred to

class Widget2 : public QWidget
{
Q_OBJECT
public:
    Widget2(QWidget* parent = nullptr);

private:
    QPushButton button;
};

does not necessarily mean that the button is created on the stack. It depends on how the instance of Widget2 is created. If it is on the stack, then also button is on the stack. If it is newed on the heap, then also button is on the heap. The difference from the "normal" case where button is declared and kept as a pointer is that in the example above they are allocated together in one allocation. Yes, it is negligibly faster but it is just a premature optimization. Allocation of widgets will never ever be your bottleneck, believe me. Btw. most objects in Qt are implemented using PIMPL idiom to preserve binary compatibility, so they internally allocate their private objects on the heap anyway.

Yes, you can keep your button as a NON-pointer member. This is legal. But it is not wise. There is an issue with this approach: you must #include all the subwidgets in the header and you cannot forward-declare them. Which definitely makes you compile time longer. But that is not the main problem, you can wait a little. The bigger problem is that you are introducing building dependencies which makes your code less composable. Imagine you have your custom widgets split into multiple libraries - then everything what you #include in your headers visible to other modules also requires to be visible to these modules. This soon becomes unmanageable mess because you will not be able to hide/encapsulate implementation details. And the fact that your widget uses some button certainly is an implementation detail. Just keep as little details in your header as possible. Keeping a pointer (can be forward-declared) is definitely less of a burden than keeping a non-pointer member (must be #included!). Moreover if you keep a pointer, then you can use substitution principle and this pointer can actually point to any subclass instance. In little toy applications or school homeworks this does not matter but when you are designing a really large application wit lots of modules and submodules organized as libraries or when you are writing libraries which are to be used by others, then these things REALLY DO matter. This makes the difference between well maintainable and totally unmaintainable code.

In my practice I have encountered only two usecases where widgets are actually allocated on the stack. First is when they are created in main() where you can write:

int main() {
  QApplication app;
  MainWindow w; // it is really on the stack
  w.show();
  return app.exec();
  // here is the main window automatically destroyed
}

Another case is when you open a display a modal dialog with a blocking event loop using exec().

void showSomeMessage() {
  QMessageBox box; // yes, we should set a parent here, for sake of simplicity I omitted it
  // etc. set text and button here
  box.exec(); // blocking here, we are waiting for user interaction
  // end of scope - we are done here, we do not need the message box any more 
  // so it is perfectly fine it gets automatically deleted here
}

These cases are perfectly fine. Actually, as you can see blocking exec() in both these cases, they are very similar.

Conclusion: it is better to declare member QObjects or QWidgets via pointers and always forward-declare them in your headers instead of #includeing their headers. This way you can design more composable, maintainable and future-proof code. You will not see any significant difference in small toy projects, but you will benefit from this good practice when your codebase grows larger. Even better habit is to keep them via QPointer (which a special Qt weak pointer), which has twofold benefit: 1) it is automatically initialized to nullptr and 2) when the objects gets destroyed, the pointer is automatically set to nullptr so you can easily check if it is alive. Plus QPointer provides implicit conversions to raw pointer so it does not affect the way you write your code. It is a very handy class.

  •  Tags:  
  • c qt
  • Related