On Friday, along with the Qt for Symbian integration, we got a new smart pointer class in Qt, called QScopedPointer. Harald, one of the class’s author, blogged about it, which prompted a lot of comments asking why we have those classes and what’s the difference between the ones we have.
Before we can find out why we have those classes, we need to know the classes we have. So, count with me, in chronological order:
- QPointer (4.0)
- QSharedDataPointer (4.0)
- QExplicitlySharedDataPointer (4.3/4.4)
- QtPatternist::AutoPtr (internal class, 4.4)
- QSharedPointer (4.5)
- QWeakPointer (4.5)
- QGuard (internal class, 4.6)
- QScopedPointer (4.6)
Note: QExplicitlySharedDataPointer was introduced in 4.3, but the API was made public and documented in 4.4
That many, huh?
Each and every case has its use and they all (except one) are still valid today.
Shared pointer versus shared data
First, let’s get one thing straight: there’s a difference between sharing pointers and sharing data. When you share pointers, the value of the pointer and its lifetime is protected by the smart pointer class. In other words, the pointer is the invariant. However, the object that the pointer is pointing to is completely outside its control. We don’t know if the object is copiable or not, if it’s assignable or not.
Now, sharing of data involves the smart pointer class knowing something about the data being shared. In fact, the whole point is that the data is being shared and we don’t care how. The fact that pointers are being used to share the data is irrelevant at this point. For example, you don’t really care how Qt tool classes are implicitly shared, do you? What matters to you is that they are shared (thus reducing memory consumption) and that they work as if they weren’t.
Strong versus weak pointer referencing
The difference between a strong and a weak reference is whether the existence of the smart pointer class on a given pointer guarantees that the object will not get deleted. In other words, if you have this smart pointer, are you sure that this will always remain valid (provided, of course, everyone is playing by the same rules)?
Some of the pointer classes above don’t guarantee that. If they don’t guarantee that the object remains valid, their main purpose in life is to tell you whether the object has been deleted already or not. Some classes may provide an additional feature that allows you to promote a weak pointer to a strong one, thus guaranteeing that it won’t get deleted anymore.
The Qt smart pointer classes
QPointer is a weak pointer class and it shares the pointer value, not the data. It only operates on QObject and QObject-derived classes. This class was added in Qt 4.0 and is the direct upgrade of Qt 3’s QGuardedPtr (and Qt 2’s QGuardedPtr). Like its predecessors, QPointer suffers from broken constness support and shows its age.
Its sole purpose in life is to tell you whether the QObject has been deleted already or not. But, unlike Qt 2 and Qt 3, the QObject of Qt 4 can live in several threads. That means QPointer has one serious flaw: it lets you know whether the object has been deleted, but it makes no guarantee about the next line! For example, the following code could be in trouble:
QPointer<QObject> o = getObject(); // [...] if (!o.isNull()) o->setProperty("objectName", "Object");
Even if isNull() returns false, there’s no guarantee that the object won’t get deleted by the next line.
Therefore, QPointer can only be used to access the object if you can guarantee, by external means, that the object won’t get deleted. For example, QWidget and its descendents can only be created, manipulated and deleted in the GUI thread. If your code is running on the GUI thread or has that thread blocked, then QPointer usage is safe.
Now this is a nice little class. It’s actually by far the most important of the smart pointer classes in Qt for its ingeniuty: it provides implicit sharing, with thread-safe copy-on-write. It requires that your class have a member called ref, which offers a function called ref() for increasing the reference count, and another called deref() that decreases that reference count and returns false when it drops to zero. If you derive your class from QSharedData, you get exactly that. Moreover, the size of a QSharedDataPointer object is exactly the size of a pointer. That means you can replace normal pointers with it in your code without breaking Binary Compatibility.
This class is the basis of all Qt value-type, implicit-shared, thread-safe copy-on-write recent classes, like QNetworkProxy. The only reason why it isn’t used in the base classes like QByteArray, QString and QList is that those classes were developed before this class was made. There’s nothing technically stopping the retrofitting of those classes with QSharedDataPointer.
So QSharedDataPointer is a strong smart pointer class, sharing data.
This class is exactly like QSharedDataPointer (so it’s a a strong smart pointer class, sharing data), with the only difference that it never implicitly causes the detach. With QSharedDataPointer, any non-const access will cause the data to be copied. With QExplicitlySharedDataPointer, you have to call detach() for that to happen. This allows you to implement explicitly-shared data classes — which Qt doesn’t have anymore, but Qt 3 did in QMemArray (so it’s present in Qt4’s Qt3Support Q3MemArray).
But it also allows you to have finer-grained control of the detaching operation. In fact, if the Qt Tool classes were to be retrofitted with a smart pointer class, they’d be using QExplicitlySharedDataPointer instead. Using this class allows the code to delay the detaching until the very last moment, ensuring that no unnecessary memory access happens.
This is an internal class used by the QtXmlPatterns module. It’s basically your stock, dumb pointer wrapper. So it implements a strong pointer. It doesn’t share it, though.
The reason this class exists in the first place is that the QtXmlPatterns module makes extensive use of exceptions internally. To survive exceptions being thrown without leaking memory, a pointer wrapper is indicated. QtXmlPatterns also uses reference-counted classes, for which AutoPtr is not indicated — in that case, it uses QExplicitlySharedDataPointer.
This class was created as a response to QtPatternist::AutoPtr. When I started writing it, I intended for it to be ready for Qt 4.4 and replace the use of the internal class that Frans had written and what I perceived as a misuse of QExplicitlySharedDataPointer. QtXmlPatterns was using QExplicitlySharedDataPointer not for sharing data, but for sharing pointers. The objects it was sharing were not copiable. A later investigation, however, revealed that QtScript, Phonon, and Solid were using it for the same purpose. (In fact, QtScript introduced QExplicitlySharedDataPointer for that purpose in 4.3)
So QSharedPointer was shelved for 4.4, but was reborn in 4.5. It implements a strong smart pointer class, sharing the pointer. It has all the features you may want in a modern pointer class: it is polymorphic, it supports static, const, and dynamic casts, it implements atomic reference-counting and thread-safe semantics, it supports custom deleters. But note that, when I say it implements thread-safe semantics, it’s only for the pointer itself: remember it shares the pointer, not the data.
It comes with a cost, though: to support polymorphism correctly, the size of QSharedPointer is actually twice the size of a normal pointer. This means you cannot maintain binary compatibility while replacing a normal pointer with it in public parts of your API. You can use it internally in your code, though.
This is the companion class of QSharedPointer. If that implements a strong control of the pointer, QWeakPointer is a weak smart pointer class, sharing the pointer. It works in tandem with QSharedPointer: QWeakPointer can only be created from a QSharedPointer and they let you know when a QSharedPointer has been deleted.
They can be promoted to QSharedPointer, though, in a thread-safe manner. So it allows us to rewrite the code above to be safer:
QWeakPointer<Data> weak(getSharedPointer()); // [...] QSharedPointer<Data> ptr = weak; if (!ptr.isNull()) ptr->doSomething();
In this case, the promotion of a QWeakPointer to a QSharedPointer will either succeed or it won’t. But that’s a thread-safe decision: if it does succeed, then the resulting object is guaranteed not to get deleted, while you hold the ptr reference (again, as long as everyone plays by the same rules).
With 4.6, I added a nifty new feature to QWeakPointer: its ability to track QObjects as well, without passing through a QSharedPointer. It can be used to determine whether a QObject-derived object has been deleted already or not. So it implements a weak pointer class sharing the pointer value for QObject-derived classes. Sounds familiar? Yes, that’s the idea: you can replace the old, slow QPointer with a faster, modern alternative. Just be careful that the size of QWeakPointer is not the same size of QPointer.
This is another internal class. It was added to replace QPointer because that is very slow (it uses a global, mutex-protected QHash, which must be accessed by every QObject destructor). It’s actually what prompted me to write the QWeakPointer QObject-tracking feature. But it’s in a state of flux: we don’t know whether we’re going to keep or even use this class. Anyway, it’s internal, so you really don’t care about it.
This is the new kid in the block: it implements a non-shared strong pointer wrapper. It was created because of our attempt at handling the Symbian platform’s exceptions in our container classes: we needed a way to free resources without writing try/catch everywhere. A scoped pointer provides a very nice way to do RAII. In fact, QScopedPointer is actually a full replacement for QtXmlPattern’s QtPatternist::AutoPtr. Both implement the same functionality, so the internal one can be dropped.
Some people commented in Harald’s blog that we could’ve used QSharedPointer. Actually, we couldn’t: QSharedPointer has the size of two pointers, but we’re replacing Qt code that has the size of one pointer, so we needed a class that fits into that space. That’s also the reason why QScopedPointer has a custom deleter as a template parameter, as opposed to a parameter to the constructor (like QSharedPointer does): it has no space in those 4 or 8 bytes to store the custom deleter.
What’s more, QSharedPointer implements atomic reference-counting. Never mind the fact that it’s atomic: the reference counting is absolutely unnecessary for the cases that QScopedPointer is trying to solve.
Why not C++0x? Why not TR1? Why not Boost?
Some people in Harald’s blog suggested we should use std::shared_ptr (C++0x) or std::tr1::shared_ptr (TR1). I’m sorry, but those people didn’t see very far: we can’t use C++0x. It’s not even approved and there are only two compilers that implement initial support for it (GCC since 4.3 and MSVC 2010, which is in beta). It’s not even funny to suggest using C++0x for Qt at this point. You can use it for your own code, but we can’t use it in Qt.
TR1 has been implemented by more compilers. Unfortunately, not enough. We have to deal with compilers that haven’t implemented C++98 fully yet — or people who don’t bother to change their compiler settings. For example, the latest version of the Sun Studio compiler on Solaris (Sun Studio 12, with CC 5.10) still comes with the RogueWave implementation of pre-C++98 STL. If you read Sun’s article comparing RW stdlib to stlport4, you’ll see why they still keep the 11-year-old library as default. But the point is that they do, which means we have to deal with it. (Fortunately, other compiler vendors provide newer STL implementations, even though their compilers are sometimes far too picky)
That means the only smart pointer from STL we can use in Qt is std::auto_ptr. And even then there are issues (RW stdlib doesn’t implement member templates).
That leaves Boost. And there are some nice things in Boost: boost::shared_ptr, boost::intrusive_ptr, boost::scoped_ptr, etc. In fact, there are a lot of nice things in Boost. Very often I see things there that I’d like to have in Qt. Of course, that means I can just add said feature to Qt as well. There’s nothing stopping me, aside from, well, my day job 🙂
One of the main problems with boost is that it provides an “un-Qt-ish” API — to say the least; I prefer calling it “horrible API”, but that’s a statement of opinion, not fact. Even if Boost’s API is intuitive to some people, it represents a departure from Qt’s API. That means those people using Qt and Boost need to learn Boost’s way of doing things as well, their naming of functions, etc.
At the very least, we’d have to wrap Boost’s API around with a Qt shell. But if we go further, we see that Qt loses control of an important piece of its technology. We then have to deal with whatever problems they have, at their schedules. Also, it adds a dependency to Qt, one we can’t justify because they don’t promise binary compatibility (cursory search over the web; please correct me if I’m wrong). Binary compatibility is the other of the main problems.
So, no, Boost is not an option either.
So Qt has too many smart pointer classes. Or does it?
In fact, Qt has only these pointer classes if you exclude the internal classes and you deprecate QPointer:
|QSharedDataPointer / QExplicitlySharedDataPointer||Implements sharing of data (not of pointers), implicitly and explicitly, respectively|
|QSharedPointer||Implements reference-counted strong sharing of pointers|
|QWeakPointer||Implements reference-counted weak sharing of pointers|
|QScopedPointer / QScopedArrayPointer||Implements non-reference-counted strong pointer wrapper (QSharedPointer’s little brother)|
Update 1: QExplicitlySharedDataPointer can be used to implement reference-counted sharing of pointers when the target class includes the reference counter (similar to boost::intrusive_ptr)
Update 2: QScopedPointer is really based on the API of boost::scoped_ptr (but is not a copy); QSharedPointer and QWeakPointer were developed from the scratch.