> On 1 Feb 2020, at 19:20, Daniel Teske <[email protected]> wrote:
>> Parent-child relationship is not going to go away, it has purpose beyond 
>> object lifetime: the visual hierarchy is encoded in the parent/child 
>> relationship of widgets, for example.
>> 
>> Making ownership of objects, and assumptions regarding their life-cycle, 
>> explicit in code is a good thing though. Using smart pointers is the 
>> idiomatic C++ way to do so. But it doens’t have to be the only way; a call 
>> to QObject::setParent is IMHO just as explicit, and parent/child in Qt is a 
>> concept you have to learn either way.
>> 
>> But sometimes our APIs are messy. QTabWidget::addTab transfers ownership of 
>> the QWidget*; QTabWidget::addAction does not transfer ownership of the 
>> QAction*. Unless you understand fairly well how QAction works, or unless you 
>> read/remember the documentation, it’s impossible to see from the code what 
>> happens wrt to object ownership.
>> 
>> API naming is not a scalable way to make the distinction, so finding how we 
>> can use smart pointers to make it clear in the code when ownership is 
>> transferred is a good idea.
>> 
>> Perhaps the pragmatic approach is to address this in those APIs that take a 
>> QObject* but don’t transfer ownership. I believe those are much fewer. 
>> Otherwise, the convention can be that any API that takes a raw pointer takes 
>> ownership. How this is implemented internally doesn’t matter.
>> 
>> 
> 
> I have already invested quite a bit of time and thinking into how to add 
> unique_ptr apis to Qt. And I encourage everyone to go back and read my 
> previous mails, and/or the actual patch I produced.
> 
> Because I have already presented a design that is:
> 
> * Backwards compatible, existing code continues to work.
> * And adds a lot of memory safety to Qt. Though not without a loop-hole.
> * And actually exists as a experimental patch, not just some abstract idea.
> 
> To explain it once more, the key insight is the following invariant:
> 
> A QObject[1] is either owned by its parent, or by a unique_ptr[2].
> 
> [1]: Some classes use the same parent-child concept as QObject. 
> [2]: Stack allocated objects are essentially equivalent to unique_ptr held 
> ones.
> 
> That implies that a unique_ptr owned QObject should have no parent.
> 
> That invariant ensures that there's exactly one owner for each object, and 
> thus ensures that every object is deleted. 
> 
> But it doesn't guarantee that the owner is what the user expected. 
> 
> So to take your examples:
> 
> QTabWidget::addAction: This never transfers ownership, thus the invariant 
> holds.
> 
> parent->addTab(tab): This has three cases:
> 
> * tab is owned already by parent: Invariant holds
> * tab is owned by a different parent: Invariant holds
> 
> And the last case, and that's the small loop hole in the design:
> * tab has no parent
> 
> That means, the child is owned by a unique_ptr, and the user must have called 
> .get() on it. 
> 
> Thus the small case where the user has to be cautious is when they have a raw 
> pointer for a object owned by a unique_ptr. Luckily with the design I have 
> this is should be rare. 
> 
> In addition to the existing QTabWidget::addTab, the patch then adds:
> 
> QTabWidget::addTab(unique_ptr<>) for which the invariant obviously holds.
> 
> Now to take your next example: setParent is not fine. setParent can be used 
> in 4 different ways:
> 
> child->setParent(child->parent()): Invariant holds
> child->setParent(newParent): Invariant holds
> child->setParent(nullptr): Nope
> orphan->setParent(parent): Nope
> 
> The solution is I have is to replace setParent with two functions: adoptChild 
> + releaseChild, which both ensure that the invariant always holds.
> 
> And to deprecate setParent. I actually made that deprecation opt-in, because 
> of the next part:
> 
> Construction of QObjects:
> 
> The goal is to enable modern C++, meaning that users can choose to never use 
> "new". 
> 
> The first problem is then that a ctor such as this:
> 
> QTabWidget(QWidget *parent = nullptr)
> 
> can be used via make_unique:
> 
> auto child = make_unique<QTabWidget>(someParent);
> 
> That call would then violate the invariant, and thus in my patch I split up 
> the ctor of QTabWidget into 2:
> 
> QTabWidget()
> QTabWidget(QWidget *parent)
> 
> This is obviously backwards compatible. 
> The first ctor can be used without violating the invariant, whereas the 
> second one violates it. And thus it's again optionally deprecated.


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..

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).



> 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.


>   echoGroup
> ->setLayout(std::move(echoLayout));
> And obviously the use can choose whatever they like, though I would recommend 
> the first variant. 
> 
> Imho this is the nicest aspect of this design. Adding lots of unique_ptr apis 
> lead to no usage of unique_ptrs. Instead, there's just a clearer way to 
> construct the usual parent-child relationships.
> 
> There are some details I'm omitting here, and some apis that needs more work 
> but that's the core idea.



So, how do we get a weak “observer" pointer into the layout, and reset the 
unique_ptrs only in the setLayout line when the widgets are de-facto no longer 
owned by the unique_ptr?


Cheers,
Volker


_______________________________________________
Development mailing list
[email protected]
https://lists.qt-project.org/listinfo/development

Reply via email to