[Development] unique_ptr and Qt
Daniel Teske
qt at squorn.de
Tue Jun 5 14:40:52 CEST 2018
Hi,
I've done some research into how supporting unique_ptr in Qt might look
like.
I have a patch which adds apis using unique_ptr for ownership transfer
to almost all of QtBase and a patch for Qt Creator to show how using
those apis looks like.
Qt: https://codereview.qt-project.org/231543
Qt Creator: https://codereview.qt-project.org/231545
It's nowhere finished, and I have no expectations of it going into Qt in
the near future, nor the intention to finish the patch. The intent was,
to have some fun, to explore the space of what is possible, to show how
it could look like and to maybe start a discussion on what Qt can and
should do in that direction. Also I run out of time to properly polish it.
This patch is backwards compatible, existing code continues to work
exactly the same and has a very high degree of source compatibility. It
allows users to opt-in to warnings that indicate unclear ownership
semantics and allows for piece by piece porting of code. It requires
C++17 though. And unfortunately due to a bug MSVC 15.7 is not good
enough. That bug in guaranteed copy elision is supposed to be fixed in 15.8.
The additional apis are enabled via a configure flag and are inline
functions.
The core idea is this invariant:
A Qt Object either is owned by a unique_ptr, is owned by a parent, or is
allocated on the stack. (A stack allocated object, might or might not
have a parent.)
Thus the first addition are 3 new functions for creating objects, all
very similar to std::make_unique.
std::unique_ptr<T> qmakeUnique<T>(Args ...);
T* QObject::makeChild(Args ...);
T makeOnStack(ParentType, Args ...);
Those functions ensure that the returned object observe the invariant.
That is, qmakeUnique passes a nullptr as the parent pointer to T. And
widget->makeChild(...) passes widget as the parent pointer. Thus if the
constructor uses the parent pointer to set it's parent, the invariant
holds and is ensured at compile time. Some types, e.g. QEvent,
QTreeWidgetItem are treated a bit differently, see the patch for details.
I'll discuss various classes of functions:
* Functions "taking" ownership, e.g. QLayout::addWidget( child )
Note: Qt functions that "take" ownership, don't actually always take
ownership. If the passed in argument is already owned by the right
parent, ownership is not transferred.
To ensure that the invariant holds, there are two cases to consider:
a) The child is owned by a parent. => Use the addWidget(QWidget
*rawPointer) overload
b) The child is owned by a unique_ptr => Use the new
addWidget(std::unique_ptr<QWidget> widget) overload
For example this code is then possible:
auto label = qmakeUnique("label text");
layout->addWidget(std::move(label));
A undiagnosed and potential pitfall, is this code:
auto label = qmakeUnique("label text");
layout->addWidget(label.get());
which leads to double deletion. Calling a qt function that takes
ownership with a raw pointer derived from a unique_ptr is a user bug.
While obvious in this case, that isn't always so.
* Functions releasing ownership / creating new objects
There aren't actually not a lot of functions in this category. For
example QLayout::removeWidget() does not release ownership of the
widget, instead ownership is transferred if the widget is added to a
different layout.
For functions that actually do release ownership / create objects, add a
function returning a unique_ptr mostly named either "makeXXX" or
"releaseXXX".
So how do I prevent users from calling the old function?
a) Not at all, this makes the code fully backwards compatible.
b) The user can opt into deprecation warnings by defining:
QT_DEPRECATED_UNSAFE_API For Qt Creator patch, I defined that only in
the coreplugin plugin.
* Other points
I probably missed quite a few special cases, since my aim was to cover
as much as qtbase as possible, without spending too much time on each
individual class. But for the most part, there weren't that many special
cases. I left a few "TODO"s in the patch for apis that would take more
work. I don't think there was anything completely unsolvable, but quite
a few pain points.
Having unique_ptr as a vocabulary type also has some other benefits:
Some apis in Qt would really love to have a way to ensure that the
caller takes care of deleting the resource. But because there is no way
in C++98 to ensure that, they don't transfer ownership. For example, the
documentation of QTcpServer::nextPendingConnection() is:
> The socket is created as a child of the server, which means that it
is automatically deleted when the QTcpServer
<https://doc.qt.io/qt-5/qtcpserver.html> object is destroyed.
> It is still a good idea to delete the object explicitly when you are
done with it, to avoid wasting memory.
With unique_ptr there would be a way to ensure that the caller takes
ownership by returning the QTcpSocket wrapped in a unique_ptr.
Also while writing this patch, I noticed several functions whose
ownership semantics are too strange for my taste and should arguably be
changed even without adding unique_ptr. For example: void
QGraphicsGridLayout::removeAt(int index) transfers ownership of a item
that is neither a parameter nor a return value.
I also ported a very small part of Creator to use those new apis and I
actually found only one place that leaks memory. Some of the code looks
arguably better, imho makeChild does make the code easier to read. In
other parts, Creator has fancy memory ownership semantics, which are not
easily representable with unique_ptr's. And unique_ptr can be bit
awkward to use.
And as a last though, having unique_ptr as part of the vocabulary types
also allows for better apis:
Consider QScrollArea and its functions: void setWidget(QWidget *w); void
setCornerWidget(QWidget *w);
Even though they have the exact same signature, and very similar names,
their behaviour is different in how they treat the old replaced widget.
setWidget() deletes the old widget, there is afaik no way to reuse a
widget once it has been set via setWidget.
On the other hand, setCornerWidget() merely hides the old widget. (I
think the use case here is switching between multiple corner widgets.)
With unique_ptr, a imho better api would be:
unique_ptr<QWidget *> replaceWidget/replaceCornerWidget(QWidget *)
If the user does not care about the old widget, just ignoring the return
value gets the old widget deleted. If the user wants to reuse the widget
somewhere, s/he has ownership of it.
* Short guide to patch reading:
Since the patches are pretty big and most of it is pretty uninteresting,
for reading it I suggest:
1) Look at the changes in qglobal.h.
2) Look at the qwidget.h
3) Look at qtreewidgetitem.h
4) Look at the creator patch at a random file
5) Look at TODO comments which explain some of the remaining problems
* My Conclusion
Adding unique_ptr supporting apis is possible. (Though clearly this
patch would need a lot of refining.) But, it adds a lot of complexity,
and can be awkward to use. On the other hand, as more users become
accustomed with using unique_ptr in their code, the demand for Qt to
support them will grow. So imho research in that direction must happen.
Since the mail is already pretty long, I'll stop here. If you have
questions or suggestions, due to some technical difficulties, I can't
respond until Friday.
I'll be at the Contributor Summit, so if there's sufficient interest I
can add a session to the schedule.
daniel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/development/attachments/20180605/512bcc38/attachment.html>
More information about the Development
mailing list