Pregunta

I'd like to know why, in your opinion, Qt engineers decided to put the setupUi() method in every generated form.

(for those who do not know how Qt works: setupUi() is a method which allocates in dynamic memory every button, texbox, widget which is inside the form. Every element's reference is stored inside a pointer member variable which is not even initialized to nullptr and it is placed under a public: access modifier, so that also looks like a somewhat dangerous situation until you actually call setupUi())

In my newby opinion, that totally violates RAII principle, because that means any Ui_Whatever class is actually not constructed by its constructor call: constructor just allocates the class itself, but it is unusable till we call Ui_Whatever::setupUi().

Which could be the design reason behind the choose of having:

  • an empty compiler-generated default-constructor
  • a method which do the real construction

(I'm asking 'cause I can't figure out myself any valid reason at all)

Thanks in advance!!

¿Fue útil?

Solución

I agree, that it totally violates RAII principles. But I found a fairly easy workaround for that:

template<typename UiClass> class QtRaiiWrapper : public UiClass
{
public:
    QtRaiiWrapper(QMainWindow *MainWindow)
    {
        setupUi(MainWindow);
    }
};

...

#include "ui_Main.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
private:
    // Ui_MainWindow m_Ui; // no RAII
    QtRaiiWrapper<Ui_MainWindow> m_Ui; // RAII
};

(It might need some improvements, but this is the basic idea and works for me pretty well.)

Some more discussion on the topic:

Qt's choice of implementing the initialization via setupUi does not only lack RAII for this component, it can also make RAII impossible for other parts of your program, which is double bad. In my case, another object used the value of an GUI-Element for initialization (this way I could define the initial value in QtDesigner, rather than using a fake value there and overwriting it upon startup), but could not use RAII for that, because the GUI would only be initialized inside the body of the constructor of my MainWindow.

Therefore I disagree with Kuba Ober, you have not full control of when exactly you want the GUI to be initialized, as you can only do it inside the constructor body and not the initialization list (and following RAII, the initialization list is the place where perhaps most of all the initialization should happen). But luckily, C++ is awesome and we can write a workaround for that (as posted above). I also think this is far better than changing the way uic because as soon as other people try to compile your Code they will see a big surprise.

Otros consejos

The Ui class is not really a full-blown C++ class, it's just a wrapper that is supposed to become an integral part of something else.

By relegating the initialization to the setupUi method, you have control over exactly when will the widgets be instantiated. You may need to execute some setup before that time, and it would be impossible to do if the Ui class's constructor was doing the instantiating.

The pointers in the Ui class are not owning pointers. You never have to explicitly delete them. All the object pointers within the Ui class point to QObject instances that are owned by other QObject instances. The root of the ownership tree is the widget you pass to setupUi. The RAII is effectively delegated to the owning QObject instance; there's no reason to duplicate that in setupUi.

If you insist on having an initialized Ui object, you can modify the uic tool to nullptr-initialize the pointers in the constructor. It's a 5-10 line change that I leave to the reader's fancy :) It's a waste of CPU cycles, as far as I'm concerned, but perhaps it could be useful in a debug build.

The problem behind one or two-phased initialization of UI sub-object is that the code in setupUi(this) needs the dynamic type of the form (this parameter) to be the real static type (class) of the form. But during initialization of parent classes and members it is of static type of parent class, not the form itself. So all virtual function calls and RTTI can work different when called in the initialization phase of form object construction. After opening brace of constructor function the dynamic and static types of the object are always same.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top