IceFire IceFire - 1 month ago 19
C++ Question

QT-specific difference of stack vs. heap attributes?

Usually, when writing C++ code, I would hold objects always as normal attributes, thus utilizing RAII. In QT, however, the responsibility for deleting objects can lie within the destructor of

QObject
. So, let's say we define some specific widget, then we have two possibilities:

1) use QT's system

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

private:
QPushButton* myButton; // create it with "new QPushButton(this);"
};


2) use RAII

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

private:
QPushButton button; // normal RAII
};


Usually, I use the first method. It seems that something could work better if a parent knows its children not only by layout. But thinking about it... The real reason for this is not quite clear to me.

I know that the stack is limited. But let's say that this does not play a role here. After all, the stack is not that small.

Answer

It seems that something could work better if a parent knows its children not only by layout.

You are right. A QObject's parent is not only used for memory management purposes, this answer sums some of its other usages. The most important ones here are the ones of QWidget's (since you are concerned about adding member QWidget's). So, if you use the second approach the way you are writing it. Here are some of the issues you might get:

  • Suppose you are instantiating Widget1 and displaying it in your main function like this:

    Widget1 w;
    w.show();
    

    This will display an empty widget without the button inside it. As opposed to the behavior when button is a child of the Widget1 object, where calling show() displays the widget parent with all of its children inside.

    Similar issue happens when using setEnabled, setLayoutDirection(), ...

  • button.pos() won't return coordinates relative to Widget1, in fact the button is not even displayed inside it. Same issue when using move().

  • The event system might now work as expected. So, if the member widget does not handle some mouse/keyboard event, the event won't propagate to the parent widget (since there is no parent specified).

But the second approach can be written to utilize the parent relationship with RAII, so that above issues are avoided:

class Widget2 : public QWidget
{
public:
    explicit Widget2(QWidget* parent = nullptr):QWidget(parent){}

private:
    QPushButton button{this}; //C++11 member initializer list
};

Or, in pre-C++11:

class Widget2 : public QWidget
{
public:
    //initialize button in constructor, button's parent is set to this
    explicit Widget2(QWidget* parent = nullptr):QWidget(parent), button(this){}

private:
    QPushButton button;
};

This way there aren't any differences to the Qt Framework between both approaches. In fact, using the second approach can have better performance because it avoids dynamic allocation when unnecessary. So, you might be writing your widgets like this:

class Widget : public QWidget
{
public:
    explicit Widget(QWidget* parent = nullptr):QWidget(parent){
        //add widgets to the layout
        layout.addWidget(&button);
        layout.addWidget(&lineEdit);
        layout.addWidget(&label);
    }
    ~Widget(){}

private:
    //widget's layout as a child (this will set the layout on the widget)
    QVBoxLayout layout{this};
    //ui items, no need to set the parent here
    //since this is done automatically in QLayout::addWidget calls in the constructor
    QPushButton button{"click here"};
    QLineEdit lineEdit;
    QLabel label{"this is a sample widget"};
};

This is perfectly fine, one might say that those child widgets/objects will be destroyed twice, first when they are out of scope and a second time when their parent is destroyed, making the approach unsafe. This is not an issue as once a child object is destroyed it removes itself from its parent's children list, see docs. So, each object will be destroyed exactly once.

Comments