[Development] The future of smart pointers in Qt API
Daniel Teske
qt at squorn.de
Mon Feb 3 19:09:36 CET 2020
> Hi Daniel,
>
> I like many things in this proposal, especially the principles; thanks for that!
>
> Where I’m on the fence is the replacing of setParent with “adoptChild”. I think of “parent” as a property of an object, so setParent/parent accessors are the API that fits into my mental model. I have a hard time thinking about “list of children” as that property..
Well this should not compile. (Or rather be optionally deprecated.)
unique_ptr<> a;
a->setParent(b);
>
> But I think the example below shows exactly the problem <cliff_hanger>
>
>> Now for some classes the parent ptr has additional meaning beyond just being the owner. E.g. for the layout classes. For that reason there's a bit of infrastructure, which makes this work:
>>
>> parent->makeChild<QTabWidget>();
>>
>> or to take a more complex example:
>>
>> parent->makeChild<QLabel>("Hello World!");
>>
>> (The infrastructure is a third constructor, and a forwarding function defined via Q_OBJECT)
>>
>> For example, converting some random code on https://doc.qt.io/qt-5/qtwidgets-widgets-lineedits-example.html to use makeChild would look like this:
>>
>> Window::Window(QWidget
>> *parent)
>> :
>> QWidget
>> (parent)
>> {
>> QGroupBox *echoGroup = makeChild<QGroupBox>(tr("Echo"));
>> QLabel *echoLabel =
>> echoGroup
>> ->makeChild<QLabel>(tr("Mode"));
>> QComboBox *comboBox =
>> echoGroup
>> ->makeChild<QComboBox>();
>> [...]
>> QGridLayout *echoLayout = echoGroup->makeChild<QGridLayout>();
>>
>> echoLayout->addWidget(echoLabel, 0, 0);
>>
>> echoLayout
>> ->addWidget(echoComboBox, 0, 1);
>>
>> echoLayout
>> ->addWidget(echoLineEdit, 1, 0, 1, 2);
>>
>
> This works, but now you are encoding the visual hierarchy of the widgets in two places: when asking the presumed parent to create the widgets; and when setting up the layout, which might do things differently (esp once you start refactoring complex UIs).#
That's not a problem. The code simply behaves as if you would have used
the current constructors that take a parent parameter.
Ownership transfers between different qt parents don't violate the
invariant. And they continue to work just like before.
>
>
>
>> Or if using unique_ptrs:
>>
>> Window::Window(QWidget
>> *parent)
>> :
>> QWidget
>> (parent)
>> {
>> auto echoGroup = std::make_unique<QGroupBox>(tr("Echo"));
>> auto echoLabel = std::make_unique<QLabel>(tr("Mode"));
>> auto comboBox = std::make_unique<QComboBox>();
>> [...]
>> auto echoLayout = std::make_unique<QGridLayout>();
>>
>> echoLayout->addWidget(std::move(echoLabel), 0, 0);
>>
>> echoLayout
>> ->addWidget(std::move(echoComboBox), 0, 1);
>>
>> echoLayout
>> ->addWidget(std::move(echoLineEdit), 1, 0, 1, 2);
>
> I don’t think these lines above are correct.
>
> QBoxLayout::addWidget does *not* take ownership. Widgets are reparented to their correct parent once you call QWidget::setLayout in the next line, and QLayout operates on QLayoutItems that hold a weak pointer to the widgets that the layout places.
It's even more complicated than that, if the layout is already set on a
widget a call to QBoxLayout::addWidget does transfer ownership.
But for a parent-less QBoxLayout::addWidget(QWidget *) does not take
ownership. So whether a call to addWidget does transfer ownership is
really hard to tell.
Which I personally find surprising and I actually missed that in my
patch and Giuseppe had to point it out.
But that just means that QBoxLayout::addWidget(std::unique_ptr<>) has to
behave imho saner in that edge case and actually delete the widgets that
were added but never parented. (And I should really implement that to
show how it would work.)
There's another pain point in the patch currently, and that has to do
with the overloaded meaning of the parent ptr for QDialog. For QDialog
in addition to the memory ownership that also controls modality. The
other cases of overloaded meaning that I found in qtbase are as far as I
can tell fine.
Meaning a line such as this:
QDialog dialog(parent);
is technically a doubly owned object, because it's both owned by the
stack and by the passed parent. And that same problem makes it hard to
make this memory safe.
The solution to that, is to fix Qt here. Modality and parent should not
be coupled in this way.
daniel
More information about the Development
mailing list