BDE 4.14.0 Production release
|
Macros | |
#define | BSLSTL_SHAREDPTR_DECLARE_IF_CONVERTIBLE |
#define | BSLSTL_SHAREDPTR_DEFINE_IF_CONVERTIBLE |
#define | BSLSTL_SHAREDPTR_DECLARE_IF_COMPATIBLE |
#define | BSLSTL_SHAREDPTR_DEFINE_IF_COMPATIBLE |
#define | BSLSTL_SHAREDPTR_DECLARE_IF_DELETER(FUNCTOR, ARGUMENT) |
#define | BSLSTL_SHAREDPTR_DEFINE_IF_DELETER(FUNCTOR, ARGUMENT) |
#define | BSLSTL_SHAREDPTR_DECLARE_IF_NULLPTR_DELETER(FUNCTOR) |
#define | BSLSTL_SHAREDPTR_DEFINE_IF_NULLPTR_DELETER(FUNCTOR) |
Provide a generic reference-counted shared pointer wrapper.
Canonical header: bsl_memory.h
This component implements a thread-safe, generic, reference-counted "smart pointer" to support "shared ownership" of objects of (template parameter) ELEMENT_TYPE
. Shared pointers implement a form of the "envelope/letter" idiom. For each shared object, a representation that manages the number of references to it is created. Many shared pointers can simultaneously refer to the same shared object by storing a reference to the same representation. Shared pointers also implement the "construction is
acquisition, destruction is release" idiom. When a shared pointer is created it increments the number of shared references to the shared object that was specified to its constructor (or was referred to by a shared pointer passed to the copy constructor). When a shared pointer is assigned to or destroyed, then the number of shared references to the shared object is decremented. When all references to the shared object are released, both the representation and the object are destroyed. bsl::shared_ptr
emulates the interface of a native pointer. The shared object may be accessed directly using the ->
operator, or the dereference operator (operator *
) can be used to obtain a reference to the shared object.
This component also provides a mechanism, bsl::weak_ptr
, used to create weak references to reference-counted shared (bsl::shared_ptr
) objects. A weak reference provides conditional access to a shared object managed by a bsl::shared_ptr
, but, unlike a shared (or "strong") reference, does not affect the shared object's lifetime. An object having even one shared reference to it will not be destroyed, but an object having only weak references would have been destroyed when the last shared reference was released.
A weak pointer can be constructed from another weak pointer or a bsl::shared_ptr
. To access the shared object referenced by a weak pointer clients must first obtain a shared pointer to that object using the lock
method. If the shared object has been destroyed (as indicated by the expired
method), then lock
returns a shared pointer in the default constructed (empty) state.
This component also provides a mechanism, bsl::enable_shared_from_this
, which can be used to create a type that participates in its own ownership through the reference-counting of a shared_ptr
.
This component also provides a functor, bslstl::SharedPtrNilDeleter
, which may used to create a shared pointer that takes no action when the last shared reference is destroyed.
This component also provides a utility class, bslstl::SharedPtrUtil
, which provides several functions that are frequently used with shared pointers.
This section qualifies the thread safety of bsl::shared_ptr
objects and bsl::weak_ptr
objects themselves rather than the thread safety of the objects being referenced.
It is not safe to access or modify a bsl::shared_ptr
(or bsl::weak_ptr
) object in one thread while another thread modifies the same object. However, it is safe to access or modify two distinct shared_ptr
(or bsl::weak_ptr
) objects simultaneously, each from a separate thread, even if they share ownership of a common object. It is safe to access a single bsl::shared_ptr
(or bsl::weak_ptr
) object simultaneously from two or more separate threads, provided no other thread is simultaneously modifying the object.
It is safe to access, modify, copy, or delete a shared pointer (or weak pointer) in one thread, while other threads access or modify other shared pointers and weak pointers pointing to or managing the same object (the reference count is managed using atomic operations). However, there is no guarantee regarding the safety of accessing or modifying the object referred to by the shared pointer simultaneously from multiple threads.
There are two types of references to shared objects:
1) A shared reference allows users to share the ownership of an object and control its lifetime. A shared object is destroyed only when the last shared reference to it is released. A shared reference to an object can be obtained by creating a shared_ptr
referring to it.
2) A weak reference provides users conditional access to an object without sharing its ownership (or affecting its lifetime). A shared object can be destroyed even if there are weak references to it. A weak reference to an object can be obtained by creating a weak_ptr
referring to the object from a shared_ptr
referring to that object.
shared_ptr
provides two types of representations: an out-of-place representation, and an in-place representation. Out-of-place representations are used to refer to objects that are constructed externally to their associated representations. Out-of-place objects are provided to a shared pointer by passing their address along with the deleter that should be used to destroy the object when all references to it have been released. In-place objects can be constructed directly within a shared pointer representation (see createInplace
).
Below we provide a diagram illustrating the differences between the two representations for a shared pointer to an int
. First we create an int
object on the heap, initialized to 10, and pass its address to a shared pointer constructor, resulting in an out-of-place representation for the shared object:
Next we create an in-place representation of a shared int
object that is also initialized to 10:
The memory layouts of these two representations are shown below (where d_ptr_p
refers to the shared object and d_rep_p
refers to the representation):
An out-of-place representation is generally less efficient than an in-place representation since it usually requires at least two allocations (one to construct the object and one to construct the shared pointer representation for the object).
Creating an in-place shared pointer does not require the template parameter type to inherit from a special class (such as bsl::enable_shared_from_this
); in that case, shared_ptr
supports up to fourteen arguments that can be passed directly to the object's constructor. For in-place representations, both the object and the representation can be constructed in one allocation as opposed to two, effectively creating an "intrusive" reference counter. Note that the size of the allocation is determined at compile-time from the combined footprint of the object and of the reference counts. It is also possible to create shared pointers to buffers whose sizes are determined at runtime, although such buffers consist of raw (uninitialized) memory.
A weak pointer that is not in the empty state shares a common representation (used to refer to the shared object) with the shared (or other weak) pointer from which it was constructed, and holds this representation until it is either destroyed or reset. This common representation is not destroyed and deallocated (although the shared object itself may have been destroyed) until all weak references to that common representation have been released.
Due to this behavior the memory footprint of shared objects that are constructed "in-place" in the shared pointer representation (see above) is not deallocated until all weak references to that shared object are released. Note that a shared object is always destroyed when the last shared reference to it is released. Also note that the same behavior applies if the shared object were obtained from a class that pools shared pointer representations (for example, bcec_SharedObjectPool
).
For example suppose we have a class with a large memory footprint:
We then create an "in-place" shared pointer to an object of ClassWithLargeFootprint
using the createInplace
method of shared_ptr
. The sp
shared pointer representation of sp
will create a ClassWithLargeFootprint
object "in-place":
Next we construct a weak pointer from this (in-place) shared pointer:
Now releasing all shared references to the shared object (using the reset
function) causes the object's destructor to be called, but the representation is not destroyed (and the object's footprint is not deallocated) until wp
releases its weak reference:
If a shared object has a large footprint, and the client anticipates there will be weak references to it, then an out-of-place shared pointer representation may be preferred because it destroys the shared object and deallocates its footprint when the last shared reference is released, regardless of whether there are any outstanding weak references to the same representation.
Note that once constructed, there is no difference in type, usage, or efficiency between in-place and out-of-place shared pointers, except that an in-place shared pointer will exhibit greater locality of reference and faster destruction (because there is only one allocated block). Also note that an object created with an allocator needs to have this allocator specified as its last constructor argument, but this allocator may be different from the one passed as the first argument to createInplace
.
For example, consider the following snippet of code:
Here allocator1
is used to obtain the shared pointer representation and the in-place bsl::string
object, and allocator2
is used by the bsl::string
object (having the value "my string") for its memory allocations. Typically, both allocators will be the same, and so the same allocator will need to be specified twice.
When the last shared reference to a shared object is released, the object is destroyed using the "deleter" provided when the associated shared pointer representation was created. shared_ptr
supports two kinds of "deleter" objects, which vary in how they are invoked. A "function-like" deleter is any language entity that can be invoked such that the expression deleterInstance(objectPtr)
is a valid expression. A "factory" deleter is any language entity that can be invoked such that the expression deleterInstance.deleteObject(objectPtr)
is a valid expression, where deleterInstance
is an instance of the "deleter" object, and objectPtr
is a pointer to the shared object. Factory deleters are a BDE extension to the ISO C++ Standard Library specification for shared_ptr
. In summary:
The following are examples of function-like deleters that delete an object of my_Type
:
The following, on the other hand is an example of a factory deleter:
Note that deleteObject
is provided by all bslma
allocators and by any object that implements the bdlma::Deleter
protocol. Thus, any of these objects can be used as a factory deleter. The purpose of this design is to allow bslma
allocators and factories to be used seamlessly as deleters.
The selection of which expression is used by shared_ptr
to destroy a shared object is based on how the deleter is passed to the shared pointer object: Deleters that are passed by address are assumed to be factory deleters (unless they are function pointers), while those that are passed by value are assumed to be function-like. Note that if the wrong interface is used for a deleter, i.e., if a function-like deleter is passed by pointer, or a factory deleter is passed by value, and the expression used to delete the object is invalid, a compiler diagnostic will be emitted indicating the error.
In general, deleters should have defined behavior when called with a null pointer. In all cases, throwing an exception out of a copy constructor for a deleter will yield undefined behavior.
The following are examples of constructing shared pointers with the addresses of factory deleters:
Note that the deleters are passed by address in the above examples.
The following are examples of constructing shared pointers with function-like deleters:
Note that deleteObject
is also passed by address, but deleter
is passed by value in the above examples. Function-like deleter objects (passed by value) are stored by value in the representation and therefore must be copy-constructible. Note that even though the deleter may be passed by reference, it is a copy (owned by the shared pointer representation) that is invoked and thus the deleterInstance
is not required, nor assumed, to be non-modifiable. (For the example above, note that operator()
is intentionally not defined const
.)
shared_ptr
supports a powerful "aliasing" feature. That is, a shared pointer can be constructed to refer to a shared object of a certain type while the shared pointer representation it holds refers to a shared object of any (possibly different) type. All references are applied to the "aliased" shared object referred to by the representation and is used for reference counting. This "aliased" shared object is passed to the deleter upon destruction of the last instance of that shared pointer. Consider the following snippet of code:
In the above example, getEvents
loads into the provided bsl::list
a sequence of event objects. The enqueueEvents
function constructs an empty list and calls getEvents
to fill the list with Event
objects. Once the event list is filled, each event item is pushed as a shared pointer (presumably because events are "expensive" to construct and may be referenced simultaneously from multiple threads) onto the provided queue. Since the individual event items are contained by value within the list, pointers to them cannot be passed if it cannot be guaranteed that they will not live beyond the lifetime of the list itself. Therefore, an expensive copy operation is required to create individually-managed instances of each of the list items. The createInplace
operation is used to reduce the number of required allocations, but this might still be too expensive. Now consider the following alternate implementation of enqueueEvents
using the shared_ptr
aliasing feature:
In the implementation above, we create a single shared pointer to the Event
list, eventList
, and use that to create Event
shared pointers that are aliased to eventList
. The lifetime of each Event
object is then tied to the eventList
and it will not be destroyed until the eventList
is destroyed.
A shared_ptr
object of a given type can be implicitly or explicitly cast to a shared_ptr
of another type.
As with native pointers, a shared pointer to a derived type can be directly assigned to a shared pointer to a base type. In other words, if the following statements are valid:
then the following statements:
and:
are also valid. Note that in all of the above cases, the destructor of B
will be invoked when the object is destroyed even if A
does not provide a virtual destructor.
Through "aliasing", a shared pointer of any type can be explicitly cast to a shared pointer of any other type using any legal cast expression. For example, to statically cast a shared pointer to type A
(shared_ptr<A>
) to a shared pointer to type B
(shared_ptr<B>
), one can simply do the following:
or even the less safe C-style cast:
For convenience, several utility functions are provided to perform common C++ casts. Dynamic casts, static casts, and const
casts are all provided. Explicit casting is supported through the bslstl::SharedPtrUtil
utility. The following example demonstrates the dynamic casting of a shared pointer to type A
(shared_ptr<A>
) to a shared pointer to type B
(shared_ptr<B>
):
To test if the cast succeeded, simply test if the target shared pointer refers to a non-null value (assuming the source was not null, of course):
As previously stated, the shared object will be destroyed correctly regardless of how it is cast.
A shared_ptr
can be converted to a BloombergLP::bslma::ManagedPtr
while still retaining proper reference counting. When a shared pointer is converted to a BloombergLP::bslma::ManagedPtr
, the number of references to the shared object is incremented. When the managed pointer is destroyed (if not transferred to another managed pointer first), the number of references will be decremented. If the number of references reaches zero, then the shared object will be destroyed. The managedPtr
function can be used to create a managed pointer from a shared pointer.
A shared_ptr
also can be constructed from a BloombergLP::bslma::ManagedPtr
. The resulting shared pointer takes over the management of the object and will use the deleter from the original BloombergLP::bslma::ManagedPtr
to destroy the managed object when all the references to that shared object are released.
A weak pointer that is not in the empty state shares a common representation (used to refer to the shared object) with the shared (or other weak) pointer from which it was constructed, and holds this representation until it is either destroyed or reset. This common representation is not destroyed and deallocated (although the shared object itself may have been destroyed) until all weak references to that common representation have been released.
Due to this behavior the memory footprint of shared objects that are constructed "in-place" in the shared pointer representation (refer to the component-level documentation of bsl::shared_ptr
for more information on shared pointers with "in-place" representations) is not deallocated until all weak references to that shared object are released. Note that a shared object is always destroyed when the last shared reference to it is released. Also note that the same behavior is applicable if the shared objects were obtained from a class that pools shared pointer representations (for example, bcec_SharedObjectPool
).
For example suppose we have a class with a large memory footprint:
We then create an "in-place" shared pointer to an object of ClassWithLargeFootprint
using the createInplace
method of bsl::shared_ptr
. The sp
shared pointer representation of sp
will create a ClassWithLargeFootprint
object "in-place":
Next we construct a weak pointer from this (in-place) shared pointer:
Now releasing all shared references to the shared object (using the reset
function) causes the object's destructor to be called, but the representation is not destroyed (and the object's footprint is not deallocated) until wp
releases its weak reference:
If a shared object has a large footprint, and the client anticipates there will be weak references to it, then it may be advisable to create an out-of-place shared pointer representation, which destroys the shared object and deallocates its footprint when the last shared reference to it is released, regardless of whether there are any outstanding weak references to the same representation.
This component provides an (extended) standard-compliant implementation of std::shared_ptr
and std::weak_ptr
(section 20.7.2, [util.smartptr], of the ISO C++11 standard)). However, it does not support the atomic shared pointer interface, nor provide the C++17 interface for shared_ptr
of an array type. When using a C++03 compiler, its interface is limited to the set of operations that can be implemented by an implementation of the C++03 language, e,g., there are no exception specifications, nor constexpr
constructors, and move operations are emulated with bslmf::MovableRef
.
In addition to the standard interface, this component supports allocators following the bslma::Allocator
protocol in addition to the C++ Standard Allocators (section 17.6.3.5, [allocator.requirements]), supports "factory" style deleters in addition to function-like deleters, and interoperation with bslma::ManagedPtr
smart pointers.
The following examples demonstrate various features and uses of shared pointers.
The following example demonstrates the creation of a shared pointer. First, we declare the type of object that we wish to manage:
The createUser
utility function (below) creates a MyUser
object using the provided allocator and returns a shared pointer to the newly-created object. Note that the shared pointer's internal representation will also be allocated using the same allocator. Also note that if allocator
is 0, the currently-installed default allocator is used.
Since the createUser
function both allocates the object and creates the shared pointer, it can benefit from the in-place facilities to avoid an extra allocation. Again, note that the representation will also be allocated using the same allocator (see the section "Correct Usage of the
Allocator Model" above). Also note that if allocator
is 0, the currently-installed default allocator is used.
Note that the shared pointer allocates both the reference count and the MyUser
object in a single region of memory (which is the memory that will eventually be deallocated), but refers to the MyUser
object only.
The following examples demonstrate the use of custom deleters with shared pointers.
There are cases when an interface calls for an object to be passed as a shared pointer, but the object being passed is not owned by the caller (e.g., a pointer to a static variable). In these cases, it is possible to create a shared pointer specifying bslstl::SharedPtrNilDeleter
as the deleter. The deleter function provided by bslstl::SharedPtrNilDeleter
is a no-op and does not delete the object. The following example demonstrates the use of shared_ptr
using a bslstl::SharedPtrNilDeleter
. The code uses the MyUser
class defined in Example 1. In this example, an asynchronous transaction manager is implemented. Transactions are enqueued into the transaction manager to be processed at some later time. The user associated with the transaction is passed as a shared pointer. Transactions can originate from the "system" or from "users".
We first declare the transaction manager and transaction info classes:
The systemUser
class method returns the same MyUser
object and should not be destroyed by its users:
For enqueuing user transactions, simply proxy the information to enqueueTransaction
.
For system transactions, we must use the MyUser
objected returned from the systemUser
static
method. Since we do not own the returned object, we cannot directly construct a shared_ptr
object for it: doing so would result in the singleton being destroyed when the last reference to the shared pointer is released. To solve this problem, we construct a shared_ptr
object for the system user using a nil deleter. When the last reference to the shared pointer is released, although the deleter will be invoked to destroy the object, it will do nothing.
This example illustrates the basic syntax needed to create and use a bsl::weak_ptr
. Suppose that we want to construct a weak pointer that refers to an int
managed by a shared pointer. Next we define the shared pointer and assign a value to the shared int
:
Next we construct a weak pointer to the int
:
bsl::weak_ptr
does not provide direct access to the shared object being referenced. To access and manipulate the int
from the weak pointer, we have to obtain a shared pointer from it:
We remove the weak reference to the shared int
by calling the reset
method:
Note that resetting the weak pointer does not affect the shared pointers referencing the int
object:
Now, we construct another weak pointer referencing the shared int
:
Finally reset
all shared references to the int
, which will cause the weak pointer to become "expired"; any subsequent attempt to obtain a shared pointer from the weak pointer will return a shared pointer in the default constructed (empty) state:
Weak pointers are frequently used to break cyclical dependencies between objects that store references to each other via a shared pointer. Consider for example a simplified news alert system that sends news alerts to users based on keywords that they register for. The user information is stored in the User
class and the details of the news alert are stored in the Alert
class. The class definitions for User
and Alert
are provided below (with any code not relevant to this example elided):
Now we define an alert class, Alert
:
Even though we have released alertPtr
and userPtr
there still exists a cyclic reference between the two objects, so none of the objects are destroyed.
We can break this cyclical dependency we define a modified alert class ModifiedAlert
that stores a weak pointer to a ModifiedUser
object. Below is the definition for the ModifiedUser
class that is identical to the User
class, the only difference being that it stores shared pointer to ModifiedAlert
s instead of Alert
s:
Now we define the ModifiedAlert
class:
Note that the user is stored by a weak pointer instead of by a shared pointer:
Suppose we want to implement a peer to peer file sharing system that allows users to search for files that match specific keywords. A simplistic version of such a system with code not relevant to the usage example elided would have the following parts:
a) A peer manager class that maintains a list of all connected peers and updates the list based on incoming peer requests and disconnecting peers. The following would be a simple interface for the Peer and PeerManager classes:
The peer objects are stored by shared pointer to allow peers to be passed to search results and still allow their asynchronous destruction when peers disconnect.
b) A peer cache class that stores a subset of the peers that are used for sending search requests. The cache may select peers based on their connection bandwidth, relevancy of previous search results, etc. For brevity the population and flushing of this cache is not shown:
Note that the cached peers are stored as a weak pointer so as not to interfere with the cleanup of Peer objects by the PeerManager if a Peer goes down.
c) A search result class that stores a search result and encapsulates a peer with the file name stored by the peer that best matches the specified keywords:
The peer is stored as a weak pointer because when the user decides to select a particular file to download from this peer, the peer might have disconnected.
d) A search function that takes a list of keywords and returns available results by searching the cached peers:
First we check if the peer is still connected by acquiring a shared pointer to the peer. If the acquire operation succeeds, then we can send the peer a request to send back the file best matching the specified keywords:
e) A download function that downloads a file selected by the user:
The role of a "deleter" is to allow users to define a custom "cleanup" for a shared object. Although cleanup generally involves destroying the object, this need not be the case. The following example demonstrates the use of a custom deleter to construct "locked" pointers. First we declare a custom deleter that, when invoked, releases the specified mutex and signals the specified condition variable.
Since this deleter does not actually delete anything, void *
is used in the signature of operator()
, allowing it to be used with any type of object.
Next we declare a thread-safe queue class
. The class
uses a non-thread-safe bsl::deque
to implement the queue. Thread-safe push
and pop
operations that push and pop individual items are provided. For callers that wish to gain direct access to the queue, the queue
method returns a shared pointer to the queue using the my_MutexUnlockAndBroadcastDeleter
. Callers can safely access the queue through the returned shared pointer. Once the last reference to the pointer is released, the mutex will be unlocked and the condition variable will be signaled to allow waiting threads to re-evaluate the state of the queue.
shared_ptr
refers to the template parameter type on which it is instantiated "in name only". This allows for the instantiation of shared pointers to incomplete or void
types. This feature is useful for constructing interfaces where returning a pointer to a shared object is desirable, but in order to control access to the object its interface cannot be exposed. The following examples demonstrate two techniques for achieving this goal using a shared_ptr
.
Example 7 demonstrates the use of incomplete types to hide the interface of a my_Session
type. We begin by declaring the my_SessionManager
class
, which allocates and manages my_Session
objects. The interface (.h
) merely forward declares my_Session
. The actual definition of the interface is in the implementation (.cpp
) file.
We forward-declare my_Session
to be used (in name only) in the definition of my_SessionManager
:
Next, we define the my_SessionManager
class:
It is useful to have a designated name for the shared_ptr
to my_Session
:
We need only a default constructor:
The 3 methods that follow construct a new session object and return a shared_ptr
to it. Callers can transfer the pointer, but they cannot directly access the object's methods since they do not have access to its interface.
Now, in the implementation of the code, we can define and implement the my_Session
class:
The following shows the implementation of my_SessionManager
. Note that the interface for my_Session
is not known:
In the above example, users could infer that my_Handle
is a pointer to a my_Session
but have no way to directly access it's methods since the interface is not exposed. In the following example, my_SessionManager
is re-implemented to provide an even more opaque session handle. In this implementation, my_Handle
is redefined using void
providing no indication of its implementation. Note that using void
will require casting in the implementation and, therefore, will be a little more expensive.
In the interface, define my_SessionManager
as follows:
It is useful to have a name for the void
shared_ptr
handle.
Next we define the methods of my_SessionManager
:
Notice that my_Handle
, which is a shared pointer to void
, can be transparently assigned to a shared pointer to a my_Session
object. This is because the shared_ptr
interface allows shared pointers to types that can be cast to one another to be assigned directly.
Perform a static cast from shared_ptr<void>
to shared_ptr<my_Session>
.
Test to make sure that the pointer is non-null before using myhandle
:
#define BSLSTL_SHAREDPTR_DECLARE_IF_COMPATIBLE |
#define BSLSTL_SHAREDPTR_DECLARE_IF_CONVERTIBLE |
#define BSLSTL_SHAREDPTR_DECLARE_IF_DELETER | ( | FUNCTOR, | |
ARGUMENT | |||
) |
#define BSLSTL_SHAREDPTR_DECLARE_IF_NULLPTR_DELETER | ( | FUNCTOR | ) |
#define BSLSTL_SHAREDPTR_DEFINE_IF_COMPATIBLE |
#define BSLSTL_SHAREDPTR_DEFINE_IF_CONVERTIBLE |
#define BSLSTL_SHAREDPTR_DEFINE_IF_DELETER | ( | FUNCTOR, | |
ARGUMENT | |||
) |
#define BSLSTL_SHAREDPTR_DEFINE_IF_NULLPTR_DELETER | ( | FUNCTOR | ) |