diff options
author | Thiago Macieira <thiago.macieira@nokia.com> | 2009-08-12 09:52:15 (GMT) |
---|---|---|
committer | Thiago Macieira <thiago.macieira@nokia.com> | 2009-08-13 15:26:59 (GMT) |
commit | a2cc46c89e73d089f333423f8382eb7582699e39 (patch) | |
tree | 66fb6003a255f95af5bc2e6cb8229f5d26bd3aa6 /src | |
parent | e2bb75e66f7ef9dda6fae489ebbb30ffb5e9e37e (diff) | |
download | Qt-a2cc46c89e73d089f333423f8382eb7582699e39.zip Qt-a2cc46c89e73d089f333423f8382eb7582699e39.tar.gz Qt-a2cc46c89e73d089f333423f8382eb7582699e39.tar.bz2 |
Internal doc: explain how QSharedPointer works
Diffstat (limited to 'src')
-rw-r--r-- | src/corelib/tools/qsharedpointer.cpp | 266 | ||||
-rw-r--r-- | src/corelib/tools/qsharedpointer_impl.h | 57 |
2 files changed, 305 insertions, 18 deletions
diff --git a/src/corelib/tools/qsharedpointer.cpp b/src/corelib/tools/qsharedpointer.cpp index e3e1db6..60c7db4 100644 --- a/src/corelib/tools/qsharedpointer.cpp +++ b/src/corelib/tools/qsharedpointer.cpp @@ -107,6 +107,249 @@ cases, since they have the same functionality. See \l{QWeakPointer#tracking-qobject} for more information. + \section1 Optional pointer tracking + + A feature of QSharedPointer that can be enabled at compile-time for + debugging purposes is a pointer tracking mechanism. When enabled, + QSharedPointer registers in a global set all the pointers that it tracks. + This allows one to catch mistakes like assigning the same pointer to two + QSharedPointer objects. + + This function is enabled by defining the \tt{QT_SHAREDPOINTER_TRACK_POINTERS} + macro before including the QSharedPointer header. + + It is safe to use this feature even with code compiled without the + feature. QSharedPointer will ensure that the pointer is removed from the + tracker even from code compiled without pointer tracking. + + Note, however, that the pointer tracking feature has limitations on + multiple- or virtual-inheritance (that is, in cases where two different + pointer addresses can refer to the same object). In that case, if a + pointer is cast to a different type and its value changes, + QSharedPointer's pointer tracking mechanism mail fail to detect that the + object being tracked is the same. + + \omit + \secton1 QSharedPointer internals + + QSharedPointer is in reality implemented by two ancestor classes: + QtSharedPointer::Basic and QtSharedPointer::ExternalRefCount. The reason + for having that split is now mostly legacy: in the beginning, + QSharedPointer was meant to support both internal reference counting and + external reference counting. + + QtSharedPointer::Basic implements the basic functionality that is shared + between internal- and external-reference counting. That is, it's mostly + the accessor functions into QSharedPointer. Those are all inherited by + QSharedPointer, which adds another level of shared functionality (the + constructors and assignment operators). The Basic class has one member + variable, which is the actual pointer being tracked. + + QtSharedPointer::ExternalRefCount implements the actual reference + counting and introduces the d-pointer for QSharedPointer. That d-pointer + itself is shared with with other QSharedPointer objects as well as + QWeakPointer. + + The reason for keeping the pointer value itself outside the d-pointer is + because of multiple inheritance needs. If you have two QSharedPointer + objects of different pointer types, but pointing to the same object in + memory, it could happen that the pointer values are different. The \tt + differentPointers autotest exemplifies this problem. The same thing could + happen in the case of virtual inheritance: a pointer of class matching + the virtual base has different address compared to the pointer of the + complete object. See the \tt virtualBaseDifferentPointers autotest for + this problem. + + The d pointer is of type QtSharedPointer::ExternalRefCountData for simple + QSharedPointer objects, but could be of a derived type in some cases. It + is basically a reference-counted reference-counter. + + \section2 d-pointer + \section3 QtSharedPointer::ExternalRefCountData + + This class is basically a reference-counted reference-counter. It has two + members: \tt strongref and \tt weakref. The strong reference counter is + controlling the lifetime of the object tracked by QSharedPointer. a + positive value indicates that the object is alive. It's also the number + of QSharedObject instances that are attached to this Data. + + When the strong reference count decreases to zero, the object is deleted + (see below for information on custom deleters). The strong reference + count can also exceptionally be -1, indicating that there are no + QSharedPointers attached to an object, which is tracked too. The only + case where this is possible is that of + \l{QWeakPointer#tracking-qobject}{QWeakPointers tracking a QObject}. + + The weak reference count controls the lifetime of the d-pointer itself. + It can be thought of as an internal/intrusive reference count for + ExternalRefCountData itself. This count is equal to the number of + QSharedPointers and QWeakPointers that are tracking this object. (In case + the object tracked derives from QObject, this number is increased by 1, + since QObjectPrivate tracks it too). + + ExternalRefCountData is a virtual class: it has a virtual destructor and + a virtual destroy() function. The destroy() function is supposed to + delete the object being tracked and return true if it does so. Otherwise, + it returns false to indicate that the caller must simply call delete. + This allows the normal use-case of QSharedPointer without custom deleters + to use only one 12- or 16-byte (depending on whether it's a 32- or 64-bit + architecture) external descriptor structure, without paying the price for + the custom deleter that it isn't using. + + \section3 QtSharedPointer::ExternalRefCountDataWithDestroyFn + + This class is not used directly, per se. It only exists to enable the two + classes that derive from it. It adds one member variable, which is a + pointer to a function (which returns void and takes an + ExternalRefCountData* as a parameter). It also overrides the destroy() + function: it calls that function pointer with \tt this as parameter, and + returns true. + + That means when ExternalRefCountDataWithDestroyFn is used, the \tt + destroyer field must be set to a valid function that \b will delete the + object tracked. + + This class also adds an operator delete function to ensure that simply + calls the global operator delete. That should be the behaviour in all + compilers already, but to be on the safe side, this class ensures that no + funny business happens. + + On a 32-bit architecture, this class is 16 bytes in size, whereas it's 24 + bytes on 64-bit. (On Itanium where function pointers contain the global + pointer, it can be 32 bytes). + + \section3 QtSharedPointer::ExternalRefCountWithCustomDeleter + + This class derives from ExternalRefCountDataWithDestroyFn and is a + template class. As template parameters, it has the type of the pointer + being tracked (\tt T) and a \tt Deleter, which is anything. It adds two + fields to its parent class, matching those template parameters: a member + of type \tt Deleter and a member of type \tt T*. + + The purpose of this class is to store the pointer to be deleted and the + deleter code along with the d-pointer. This allows the last strong + reference to call any arbitrary function that disposes of the object. For + example, this allows calling QObject::deleteLater() on a given object. + The pointer to the object is kept here to avoid the extra cost of keeping + the deleter in the generic case. + + This class is never instantiated directly: the constructors and + destructor are private. Only the create() function may be called to + return an object of this type. See below for construction details. + + The size of this class depends on the size of \tt Deleter. If it's an + empty functor (i.e., no members), ABIs generally assign it the size of 1. + But given that it's followed by a pointer, up to 3 or 7 padding bytes may + be inserted: in that case, the size of this class is 16+4+4 = 24 bytes on + 32-bit architectures, or 24+8+8 = 40 bytes on 64-bit architectures (48 + bytes on Itanium with global pointers stored). If \tt Deleter is a + function pointer, the size should be the same as the empty structure + case, except for Itanium where it may be 56 bytes due to another global + pointer. If \tt Deleter is a pointer to a member function (PMF), the size + will be even bigger and will depend on the ABI. For architectures using + the Itanium C++ ABI, a PMF is twice the size of a normal pointer, or 24 + bytes on Itanium itself. In that case, the size of this structure will be + 16+8+4 = 28 bytes on 32-bit architectures, 24+16+8 = 48 bytes on 64-bit, + and 32+24+8 = 64 bytes on Itanium. + + (Values for Itanium consider an LP64 architecture; for ILP32, pointers + are 32-bit in length, function pointers are 64-bit and PMF are 96-bit, so + the sizes are slightly less) + + \section3 QtSharedPointer::ExternalRefCountWithContiguousData + + This class also derives from ExternalRefCountDataWithDestroyFn and it is + also a template class. The template parameter is the type \tt T of the + class which QSharedPointer tracks. It adds only one member to its parent, + which is of type \tt T (the actual type, not a pointer to it). + + The purpose of this class is to lay the \tt T object out next to the + reference counts, saving one memory allocation per shared pointer. This + is particularly interesting for small \tt T or for the cases when there + are few if any QWeakPointer tracking the object. This class exists to + implement the QSharedPointer::create() call. + + Like ExternalRefCountWithCustomDeleter, this class is never instantiated + directly. This class also provides a create() member that returns the + pointer, and hides its constructors and destructor. (With C++0x, we'd + delete them). + + The size of this class depends on the size of \tt T. + + \section3 Instantiating ExternalRefCountWithCustomDeleter and ExternalRefCountWithContiguousData + + Like explained above, these classes have private constructors. Moreover, + they are not defined anywhere, so trying to call \tt{new ClassType} would + result in a compilation or linker error. Instead, these classes must be + constructed via their create() methods. + + Instead of instantiating the class by the normal way, the create() method + calls \tt{operator new} directly with the size of the class, then calls + the parent class's constructor only (ExternalRefCountDataWithDestroyFn). + This ensures that the inherited members are initialised properly, as well + as the virtual table pointer, which must point to + ExternalRefCountDataWithDestroyFn's virtual table. That way, we also + ensure that the virtual destructor being called is + ExternalRefCountDataWithDestroyFn's. + + After initialising the base class, the + ExternalRefCountWithCustomDeleter::create() function initialises the new + members directly, by using the placement \tt{operator new}. In the case + of the ExternalRefCountWithContiguousData::create() function, the address + to the still-uninitialised \tt T member is saved for the callee to use. + The member is only initialised in QSharedPointer::create(), so that we + avoid having many variants of the internal functions according to the + arguments in use for calling the constructor. + + When initialising the parent class, the create() functions pass the + address of the static deleter() member function. That is, when the + virtual destroy() is called by QSharedPointer, the deleter() functions + are called instead. These functiosn static_cast the ExternalRefCountData* + parameter to their own type and execute their deletion: for the + ExternalRefCountWithCustomDeleter::deleter() case, it runs the user's + custom deleter, then destroys the deleter; for + ExternalRefCountWithContiguousData::deleter, it simply calls the \tt T + destructor directly. + + By not calling the constructor of the derived classes, we avoid + instantiating their virtual tables. Since these classes are + template-based, there would be one virtual table per \tt T and \tt + Deleter type. (This is what Qt 4.5 did) + + Instead, only one non-inline function is required per template, which is + the deleter() static member. All the other functions can be inlined. + What's more, the address of deleter() is calculated only in code, which + can be resolved at link-time if the linker can determine that the + function lies in the current application or library module (since these + classes are not exported, that is the case for Windows or for builds with + \tt{-fvisibility=hidden}). + + In contrast, a virtual table would require at least 3 relocations to be + resolved at module load-time, per module where these classes are used. + (In the Itanium C++ ABI, there would be more relocations, due to the + RTTI) + + \section3 Modifications due to pointer-tracking + + To ensure that pointers created with pointer-tracking enabled get + un-tracked when destroyed, even if destroyed by code compiled without the + feature, QSharedPointer modifies slightly the instructions of the + previous sections. + + When ExternalRefCountWithCustomDeleter or + ExternalRefCountWithContiguousData are used, their create() functions + will set the ExternalRefCountDataWithDestroyFn::destroyer function + pointer to safetyCheckDeleter() instead. These static member functions + simply call internalSafetyCheckRemove2() before passing control to the + normal deleter() function. + + If neither custom deleter nor QSharedPointer::create() are used, then + QSharedPointer uses a custom deleter of its own: the normalDeleter() + function, which simply calls \tt delete. By using a custom deleter, the + safetyCheckDeleter() procedure described above kicks in. + + \endomit + \sa QSharedDataPointer, QWeakPointer */ @@ -182,6 +425,29 @@ QWeakPointers created from QObject should never be passed to code that hasn't been recompiled. + \omit + \secton1 QWeakPointer internals + + QWeakPointer shares most of its internal functionality with + \l{QSharedPointer#qsharedpointer-internals}{QSharedPointer}, so see that + class's internal documentation for more information. + + QWeakPointer requires an external reference counter in order to operate. + Therefore, it is incompatible by design with \l QSharedData-derived + classes. + + It has a special QObject constructor, which works by calling + QtSharedPointer::ExternalRefCountData::getAndRef, which retrieves the + d-pointer from QObjectPrivate. If one isn't set yet, that function + creates the d-pointer and atomically sets it. + + If getAndRef needs to create a d-pointer, it sets the strongref to -1, + indicating that the QObject is not shared: QWeakPointer is used only to + determine whether the QObject has been deleted. In that case, it cannot + be upgraded to QSharedPointer (see the previous section). + + \endomit + \sa QSharedPointer */ diff --git a/src/corelib/tools/qsharedpointer_impl.h b/src/corelib/tools/qsharedpointer_impl.h index ba479f9..cbeb79f 100644 --- a/src/corelib/tools/qsharedpointer_impl.h +++ b/src/corelib/tools/qsharedpointer_impl.h @@ -115,6 +115,9 @@ namespace QtSharedPointer { template <class T> struct RemovePointer<QSharedPointer<T> > { typedef T Type; }; template <class T> struct RemovePointer<QWeakPointer<T> > { typedef T Type; }; + // This class provides the basic functionality of a pointer wrapper. + // Its existence is mostly legacy, since originally QSharedPointer + // could also be used for internally-refcounted objects. template <class T> class Basic { @@ -155,6 +158,12 @@ namespace QtSharedPointer { Type *value; }; + // This class is the d-pointer of QSharedPointer and QWeakPointer. + // + // It is a reference-counted reference counter. "strongref" is the inner + // reference counter, and it tracks the lifetime of the pointer itself. + // "weakref" is the outer reference counter and it tracks the lifetime of + // the ExternalRefCountData object. struct ExternalRefCountData { QBasicAtomicInt weakref; @@ -168,6 +177,9 @@ namespace QtSharedPointer { inline ExternalRefCountData(Qt::Initialization) { } virtual inline ~ExternalRefCountData() { Q_ASSERT(!weakref); Q_ASSERT(strongref <= 0); } + // overridden by derived classes + // returns false to indicate caller should delete the pointer + // returns true in case it has already done so virtual inline bool destroy() { return false; } #ifndef QT_NO_QOBJECT @@ -178,18 +190,8 @@ namespace QtSharedPointer { }; // sizeof(ExternalRefCount) = 12 (32-bit) / 16 (64-bit) - template <class T, typename Deleter> - struct CustomDeleter - { - Deleter deleter; - T *ptr; - - inline CustomDeleter(T *p, Deleter d) : deleter(d), ptr(p) {} - }; - // sizeof(CustomDeleter) = sizeof(Deleter) + sizeof(void*) - // for Deleter = function pointer: 8 (32-bit) / 16 (64-bit) - // for Deleter = PMF: 12 (32-bit) / 24 (64-bit) (GCC) - + // This class extends ExternalRefCountData with a pointer + // to a function, which is called by the destroy() function. struct ExternalRefCountWithDestroyFn: public ExternalRefCountData { typedef void (*DestroyerFn)(ExternalRefCountData *); @@ -204,13 +206,26 @@ namespace QtSharedPointer { }; // sizeof(ExternalRefCountWithDestroyFn) = 16 (32-bit) / 24 (64-bit) + // This class extends ExternalRefCountWithDestroyFn and implements + // the static function that deletes the object. The pointer and the + // custom deleter are kept in the "extra" member. template <class T, typename Deleter> struct ExternalRefCountWithCustomDeleter: public ExternalRefCountWithDestroyFn { typedef ExternalRefCountWithCustomDeleter Self; - typedef ExternalRefCountWithDestroyFn Parent; - typedef CustomDeleter<T, Deleter> Next; - Next extra; + typedef ExternalRefCountWithDestroyFn BaseClass; + + struct CustomDeleter + { + Deleter deleter; + T *ptr; + + inline CustomDeleter(T *p, Deleter d) : deleter(d), ptr(p) {} + }; + CustomDeleter extra; + // sizeof(CustomDeleter) = sizeof(Deleter) + sizeof(void*) + // for Deleter = function pointer: 8 (32-bit) / 16 (64-bit) + // for Deleter = PMF: 12 (32-bit) / 24 (64-bit) (GCC) static inline void deleter(ExternalRefCountData *self) { @@ -218,7 +233,7 @@ namespace QtSharedPointer { executeDeleter(realself->extra.ptr, realself->extra.deleter); // delete the deleter too - realself->extra.~Next(); + realself->extra.~CustomDeleter(); } static void safetyCheckDeleter(ExternalRefCountData *self) { @@ -236,8 +251,8 @@ namespace QtSharedPointer { Self *d = static_cast<Self *>(::operator new(sizeof(Self))); // initialize the two sub-objects - new (&d->extra) Next(ptr, userDeleter); - new (d) Parent(destroy); // can't throw + new (&d->extra) CustomDeleter(ptr, userDeleter); + new (d) BaseClass(destroy); // can't throw return d; } @@ -247,6 +262,10 @@ namespace QtSharedPointer { ~ExternalRefCountWithCustomDeleter(); }; + // This class extends ExternalRefCountWithDestroyFn and adds a "T" + // member. That way, when the create() function is called, we allocate + // memory for both QSharedPointer's d-pointer and the actual object being + // tracked. template <class T> struct ExternalRefCountWithContiguousData: public ExternalRefCountWithDestroyFn { @@ -289,6 +308,8 @@ namespace QtSharedPointer { ~ExternalRefCountWithContiguousData(); }; + // This is the main body of QSharedPointer. It implements the + // external reference counting functionality. template <class T> class ExternalRefCount: public Basic<T> { |