This is a smart pointer for managing the lifetime of objects allocated on freestore, with a thread safe reference count.
More...
template<class T>
class Cgu::SharedLockPtr< T >
This is a smart pointer for managing the lifetime of objects allocated on freestore, with a thread safe reference count.
- See also
- SharedPtr SharedPtrError
Class SharedLockPtr is a version of the shared pointer class which includes synchronization so that it can handle objects accessed in multiple threads (although the word Lock is in the title, by default it uses glib atomic functions to access the reference count rather than a mutex, so the overhead should be very small). Note that only the reference count is protected, so this is thread safe in the sense in which a raw pointer is thread safe. A shared pointer accessed in one thread referencing a particular object is thread safe as against another shared pointer accessing the same object in a different thread. It is thus suitable for use in different standard C++ containers which exist in different threads but which contain shared objects by reference. But:
- If the referenced object is to be modified in one thread and read or modified in another thread an appropriate mutex for the referenced object is required (unless that referenced object does its own locking).
- If the same instance of shared pointer is to be modified in one thread (by assigning to the pointer so that it references a different object, or by moving from it), and copied (assigned from or used as the argument of a copy constructor), accessed, destroyed or modified in another thread, a mutex for that instance of shared pointer is required.
- Objects referenced by shared pointers which are objects for which POSIX provides no guarantees (in the main, those which are not built-in types), such as strings and similar containers, may not support concurrent reads in different threads. That depends on the library implementation concerned (but a fully conforming C++11 library implementation is required to permit concurrent calls to the const methods of any object from the standard library without external synchronization, so long as no non-const method is called concurrently). For cases where concurrent reads are not supported, a mutex for the referenced object will be required when reading any given instance of such an object in more than one thread by dereferencing any shared pointers referencing it (and indeed, when not using shared pointers at all).
As mentioned, by default glib atomic functions are used to provide thread-safe manipulation of the reference count. However, the symbol CGU_SHARED_LOCK_PTR_USE_MUTEX can be defined so that the library uses mutexes instead, which might be useful for some debugging purposes. Note that if CGU_SHARED_LOCK_PTR_USE_MUTEX is to be defined, this is best done by textually amending the shared_ptr.h header file before the library is compiled. This will ensure that everything in the program and the library which includes the shared_ptr.h header is guaranteed to see the same definitions so that the C++ standard's one-definition-rule is complied with.
Comparison with std::shared_ptr
Most of the things that can be done by this class can be done by using std::shared_ptr from C++11/14, but this class is retained in the c++-gtk-utils library not only to retain compatibility with series 1.2 of the library, but also to cater for some cases not met (or not so easily met) by std::shared_ptr:
- Glib memory slices provide an efficient small object allocator (they are likely to be significantly more efficient than global operator new()/new[](), which generally hand off to malloc(), and whilst malloc() is good for large block allocations it is generally poor as a small object allocator). Internal Cgu::SharedLockPtr allocation using glib memory slices can be achieved by compiling the library with the --with-glib-memory-slices-no-compat configuration option.
- If glib memory slices are not used (which do not throw), constructing a shared pointer for a new managed object (or calling reset() for a new managed object) might throw if internal allocation fails. Although by default the Cgu::SharedLockPtr implementation will delete the new managed object in such a case, it also provides an alternative constructor and reset() method which instead enable the new object to be accessed via the thrown exception object so that user code can decide what to do; std::shared_ptr deletes the new object in every case.
- A user can explicitly state whether the shared pointer object is to have atomic increment and decrement-and-test with respect to the reference count so that the reference count is thread safe ('no' in the case of Cgu::SharedPtr, and 'yes' in the case of Cgu::SharedLockPtr). Using atomic functions is unnecessary if the managed object concerned is only addressed in one thread (and might cause unwanted cache flushing in certain circumstances). std::shared_ptr will generally always use atomic functions with respect to its reference count in a multi-threaded program.
In favour of std::shared_ptr, it has an associated std::make_shared() factory function which will construct both the referenced object and the shared pointer's reference count within a single memory block when the first shared pointer managing a particular object is constructed. Cgu::SharedPtr and Cgu::SharedLockPtr always allocate these separately, but this is partly mitigated by the use of glib memory slices to allocate the reference count where the --with-glib-memory-slices-no-compat configuration option is chosen.
In addition, std::shared_ptr has an associated std::weak_ptr class, which Cgu::SharedLockPtr does not (there is a Cgu::GobjWeakHandle class, but that is cognate with Cgu::GobjHandle and is only usable with GObjects), and shared_ptr objects also have some atomic store, load and exchange functions provided for them which enable concurrent modifications of the same instance of shared_ptr in different threads to have defined results.
If the library is compiled with the --with-glib-memory-slices-no-compat configuration option, as mentioned Cgu::SharedLockPtr constructs its reference counting internals using glib memory slices. Although it is safe in a multi-threaded program if glib < 2.32 is installed to construct a static SharedLockPtr object in global namespace (that is, prior to g_thread_init() being called) by means of the default constructor and/or a pointer argument of NULL, it is not safe if constructed with a non-NULL pointer value. If glib >= 2.32 is installed, global objects with memory slices are safe in all circumstances. (Having said that, it would be highly unusual to have global SharedLockPtr objects.)