BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bslstl_sharedptr

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)
 

Detailed Description

Outline

Purpose

Provide a generic reference-counted shared pointer wrapper.

Classes

Canonical header: bsl_memory.h

See also
bslma_managedptr, bslma_sharedptrrep

Description

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.

Thread Safety

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.

Shared and Weak References

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.

In-placeOut-of-place Representations

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:

int *value = new (nda) int(10);
shared_ptr<int> outOfPlaceSharedPtr(value, &nda);
Definition bslma_newdeleteallocator.h:301

Next we create an in-place representation of a shared int object that is also initialized to 10:

shared_ptr<int> inPlaceSharedPtr;
inPlaceSharedPtr.createInplace(&nda, 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):

Out-of-Place Representation In-Place Representation
---------------------------- ----------------------------
+------------+ +------------+
| | | |
| d_ptr_p ------>+-----------+ | d_ptr_p ---------+
| | | 10 | | | |
| | +-----------+ | | |
| | | | |
| d_rep_p ------>+-----------+ | d_rep_p ------>+-v---------+
| | | reference | | | |+---------+|
| | | counts | | | || 10 ||
+------------+ +-----------+ +------------+ |+---------+|
| reference |
| counts |
+-----------+

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.

Weak Pointers using "in-place" or Pooled Shared Pointer Representations

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:

/// This class has a large memory footprint.
class ClassWithLargeFootprint {
// TYPES
/// The size of the buffer owned by this `class`.
enum { BUFFER_SIZE = 1024 };
// DATA
char d_buffer[BUFFER_SIZE];
// ...
};

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":

shared_ptr<ClassWithLargeFootprint> sp;
sp.createInplace();

Next we construct a weak pointer from this (in-place) shared pointer:

weak_ptr<ClassWithLargeFootprint> wp(sp);

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:

sp.reset(); // The object's footprint is not deallocated until all weak
// references to it are released.
wp.reset(); // The release of the *last* weak reference results in the
// destruction and deallocation of the representation and the
// object's footprint.

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.

Correct Usage of the Allocator Model

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:

bslma::Allocator *allocator1, *allocator2;
// ...
shared_ptr<bsl::string> ptr;
ptr.createInplace(allocator1, bsl::string("my string"), allocator2);
Definition bslstl_string.h:1281
Definition bslma_allocator.h:457

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.

Deleters

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:

Deleter Expression used to destroy 'objectPtr'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
"function-like" deleterInstance(objectPtr);
"factory" deleterInstance.deleteObject(objectPtr);

The following are examples of function-like deleters that delete an object of my_Type:

/// Delete the specified `object`.
void deleteObject(my_Type *object);
/// Release the specified `object`.
void releaseObject(my_Type *object);
/// This `struct` provides an `operator()` that can be used to delete a
/// `my_Type` object.
struct FunctionLikeDeleterObject {
/// Destroy the specified `object`.
void operator()(my_Type *object);
};

The following, on the other hand is an example of a factory deleter:

class my_Factory {
// . . .
// MANIPULATORS
/// Create a `my_Type` object. Optionally specify a
/// `basicAllocator` used to supply memory. If `basicAllocator` is
/// 0, the currently installed default allocator is used.
my_Type *createObject(bslma::Allocator *basicAllocator = 0);
/// Delete the specified `object`.
void deleteObject(my_Type *object);
};
class my_Allocator : public bslma::Allocator { /* ... */ };

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:

my_Factory factory;
my_Type *myPtr1 = factory.createObject();
shared_ptr<my_Type> mySharedPtr1(myPtr1, &factory, 0);
bdema_SequentialAllocator sa;
my_Type *myPtr2 = new (sa) my_Type(&sa);
shared_ptr<my_Type> mySharedPtr2(myPtr2, &sa);

Note that the deleters are passed by address in the above examples.

The following are examples of constructing shared pointers with function-like deleters:

my_Type *getObject(bslma::Allocator *basicAllocator = 0);
my_Type *myPtr3 = getObject();
shared_ptr<my_Type> mySharedPtr3(myPtr3, &deleteObject);
my_Type *myPtr4 = getObject();
FunctionLikeDeleterObject deleter;
shared_ptr<my_Type> mySharedPtr4(myPtr4, deleter, &sa);

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.)

Aliasing

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:

class Event { /* ... */ };
void getEvents(bsl::list<Event> *list);
void enqueueEvents(bcec_Queue<shared_ptr<Event> > *queue)
{
bsl::list<Event> eventList;
getEvents(&eventList);
for (bsl::list<Event>::iterator it = eventList.begin();
it != eventList.end();
++it) {
shared_ptr<Event> e;
e.createInplace(0, *it); // Copy construct the event into a new
// shared ptr.
queue->pushBack(e);
}
}
Definition bslstl_list.h:739
Forward declaration required by List_NodeProctor.
Definition bslstl_list.h:1033
void swap(list &other) BSLS_KEYWORD_NOEXCEPT_SPECIFICATION(AllocTraits const_iterator begin() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_list.h:1829
iterator end() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_list.h:2829

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:

void enqueueEvents(bcec_Queue<shared_ptr<Event> > *queue)
{
shared_ptr<bsl::list<Event> > eventList;
eventList.createInplace(0); // Construct a shared pointer
// to the event list containing
// all of the events.
getEvents(eventList.get());
for (bsl::list<Event>::iterator it = eventList->begin();
it != eventList->end();
++it) {
// Push each event onto the queue as an alias of the 'eventList'
// shared pointer. When all the alias references have been
// released, the event list will be destroyed deleting all the
// events at once.
queue->pushBack(shared_ptr<Event>(eventList, &*it));
}
}

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.

Type Casting

A shared_ptr object of a given type can be implicitly or explicitly cast to a shared_ptr of another type.

Implicit Casting

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:

class A { virtual void foo(); }; // polymorphic type
class B : public A {};
B *bp = 0;
A *ap = bp;

then the following statements:

shared_ptr<B> spb;
shared_ptr<A> spa;
spa = spb;

and:

shared_ptr<B> spb;
shared_ptr<A> spa(spb);

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.

Explicit Casting

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:

shared_ptr<A> spa;
shared_ptr<B> spb(spa, static_cast<B *>(spa.get()));

or even the less safe C-style cast:

shared_ptr<A> spa;
shared_ptr<B> spb(spa, (B *)(spa.get()));

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>):

shared_ptr<A> sp1(new (nda) A(), &nda);
shared_ptr<B> sp2 = bslstl::SharedPtrUtil::dynamicCast<B>(sp1);
shared_ptr<B> sp3;
shared_ptr<B> sp4;
sp4 = bslstl::SharedPtrUtil::dynamicCast<B>(sp1);
static void dynamicCast(bsl::shared_ptr< TARGET > *target, const bsl::shared_ptr< SOURCE > &source)
Definition bslstl_sharedptr.h:6111

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):

if (sp2) {
// The cast succeeded.
} else {
// The cast failed.
}

As previously stated, the shared object will be destroyed correctly regardless of how it is cast.

Converting to and from BloombergLP::bslma::ManagedPtr

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.

Weak Pointers using "in-place" or Pooled Shared Pointer Representations

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:

/// This class has a large memory footprint.
class ClassWithLargeFootprint {
// TYPES
// The size of the buffer owned by this `class`.
enum { BUFFER_SIZE = 1024 };
// DATA
char d_buffer[BUFFER_SIZE];
// ...
};

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":

Definition bslstl_sharedptr.h:1830
void createInplace()
Definition bslstl_sharedptr.h:5448

Next we construct a weak pointer from this (in-place) shared pointer:

Definition bslstl_sharedptr.h:3705

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:

sp.reset(); // The object's footprint is not deallocated until all weak
// references to it are released.
wp.reset(); // The release of the *last* weak reference results in the
// destruction and deallocation of the representation and the
// object's footprint.
void reset() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_sharedptr.h:5358

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.

C++ Standard Compliance

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.

Usage

The following examples demonstrate various features and uses of shared pointers.

Example 1: Basic Usage

The following example demonstrates the creation of a shared pointer. First, we declare the type of object that we wish to manage:

class MyUser {
// DATA
bsl::string d_name;
int d_id;
public:
// CREATORS
MyUser(bslma::Allocator *alloc = 0) : d_name(alloc), d_id(0) {}
MyUser(const bsl::string& name, int id, bslma::Allocator *alloc = 0)
: d_name(name, alloc)
, d_id(id)
{
}
MyUser(const MyUser& original, bslma::Allocator *alloc = 0)
: d_name(original.d_name, alloc)
, d_id(original.d_id)
{
}
// MANIPULATORS
void setName(const bsl::string& name) { d_name = name; }
void setId(int id) { d_id = id; }
// ACCESSORS
const bsl::string& name() const { return d_name; }
int id() const { return d_id; }
};

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.

shared_ptr<MyUser> createUser(bsl::string name,
int id,
bslma::Allocator *allocator = 0)
{
allocator = bslma::Default::allocator(allocator);
MyUser *user = new (*allocator) MyUser(name, id, allocator);
return shared_ptr<MyUser>(user, allocator);
}
static Allocator * allocator(Allocator *basicAllocator=0)
Definition bslma_default.h:897

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.

shared_ptr<MyUser> createUser2(bsl::string name,
int id,
bslma::Allocator *allocator = 0)
{
shared_ptr<MyUser> user;
user.createInplace(allocator, name, id, allocator);
return user;
}

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.

Using Custom Deleters

The following examples demonstrate the use of custom deleters with shared pointers.

Example 2: Nil Deleters

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:

class MyTransactionInfo {
// Transaction Info...
};
class MyTransactionManager {
// PRIVATE MANIPULATORS
int enqueueTransaction(shared_ptr<MyUser> user,
const MyTransactionInfo& transaction);
public:
// CLASS METHODS
static MyUser *systemUser(bslma::Allocator *basicAllocator = 0);
// MANIPULATORS
int enqueueSystemTransaction(const MyTransactionInfo& transaction);
int enqueueUserTransaction(const MyTransactionInfo& transaction,
shared_ptr<MyUser> user);
};

The systemUser class method returns the same MyUser object and should not be destroyed by its users:

MyUser *MyTransactionManager::systemUser(
bslma::Allocator * /* basicAllocator */)
{
static MyUser *systemUserSingleton;
if (!systemUserSingleton) {
// instantiate singleton in a thread-safe manner passing
// 'basicAllocator'
// . . .
}
return systemUserSingleton;
}

For enqueuing user transactions, simply proxy the information to enqueueTransaction.

inline
int MyTransactionManager::enqueueUserTransaction(
const MyTransactionInfo& transaction,
shared_ptr<MyUser> user)
{
return enqueueTransaction(user, transaction);
}

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.

int MyTransactionManager::enqueueSystemTransaction(
const MyTransactionInfo& transaction)
{
shared_ptr<MyUser> user(systemUser(),
0);
return enqueueTransaction(user, transaction);
}
Definition bslstl_sharedptr.h:4197

Example 3: Basic Weak Pointer Usage

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:

*intPtr = 10;
assert(10 == *intPtr);

Next we construct a weak pointer to the int:

bsl::weak_ptr<int> intWeakPtr(intPtr);
assert(!intWeakPtr.expired());

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:

bsl::shared_ptr<int> intPtr2 = intWeakPtr.lock();
assert(intPtr2);
assert(10 == *intPtr2);
*intPtr2 = 20;
assert(20 == *intPtr);
assert(20 == *intPtr2);

We remove the weak reference to the shared int by calling the reset method:

intWeakPtr.reset();
assert(intWeakPtr.expired());

Note that resetting the weak pointer does not affect the shared pointers referencing the int object:

assert(20 == *intPtr);
assert(20 == *intPtr2);

Now, we construct another weak pointer referencing the shared int:

bsl::weak_ptr<int> intWeakPtr2(intPtr);
assert(!intWeakPtr2.expired());

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:

intPtr.reset();
intPtr2.reset();
assert(intWeakPtr2.expired());
assert(!intWeakPtr2.lock());

Example 4: Breaking Cyclical Dependencies

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):

class Alert;
/// This class stores the user information required for listening to
/// alerts.
class User {
bsl::vector<bsl::shared_ptr<Alert> > d_alerts; // alerts user is
// registered for
// ...
public:
// MANIPULATORS
/// Add the specified `alertPtr` to the list of alerts being
/// monitored by this user.
void addAlert(const bsl::shared_ptr<Alert>& alertPtr)
{
d_alerts.push_back(alertPtr);
}
// ...
};
Definition bslstl_vector.h:1025
void push_back(const VALUE_TYPE &value)
Definition bslstl_vector.h:3760

Now we define an alert class, Alert:

/// This class stores the alert information required for sending
/// alerts.
class Alert {
bsl::vector<bsl::shared_ptr<User> > d_users; // users registered
// for this alert
public:
// MANIPULATORS
/// Add the specified `userPtr` to the list of users monitoring this
/// alert.
void addUser(const bsl::shared_ptr<User>& userPtr)
{
d_users.push_back(userPtr);
}
// ...
};

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 ModifiedAlerts instead of Alerts:

class ModifiedAlert;
/// This class stores the user information required for listening to
/// alerts.
class ModifiedUser {
bsl::vector<bsl::shared_ptr<ModifiedAlert> > d_alerts;// alerts user is
// registered for
// ...
public:
// MANIPULATORS
/// Add the specified `alertPtr` to the list of alerts being
/// monitored by this user.
void addAlert(const bsl::shared_ptr<ModifiedAlert>& alertPtr)
{
d_alerts.push_back(alertPtr);
}
// ...
};

Now we define the ModifiedAlert class:

/// This class stores the alert information required for sending
/// alerts.
class ModifiedAlert {

Note that the user is stored by a weak pointer instead of by a shared pointer:

bsl::vector<bsl::weak_ptr<ModifiedUser> > d_users; // users registered
// for this alert
public:
// MANIPULATORS
/// Add the specified `userPtr` to the list of users monitoring this
/// alert.
void addUser(const bsl::weak_ptr<ModifiedUser>& userPtr)
{
d_users.push_back(userPtr);
}
// ...
};

Example 5: Caching

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:

/// This class stores all the relevant information for a peer.
class Peer {
// ...
};
/// This class acts as a manager of peers and adds and removes peers
/// based on peer requests and disconnections.
class PeerManager {
// DATA

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.

// ...
};
Definition bslstl_map.h:619

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:

/// This class caches a subset of all peers that match certain criteria
/// including connection bandwidth, relevancy of previous search
/// results, etc.
class PeerCache {

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.

// DATA
public:
// TYPES
typedef bsl::list<bsl::weak_ptr<Peer> >::const_iterator PeerConstIter;
// ...
// ACCESSORS
PeerConstIter begin() const { return d_cachedPeers.begin(); }
PeerConstIter end() const { return d_cachedPeers.end(); }
};

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:

/// This class provides a search result and encapsulates a particular
/// peer and filename combination that matches a specified set of
/// keywords.
class SearchResult {

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.

// DATA
bsl::string d_filename;
public:
// CREATORS
SearchResult(const bsl::weak_ptr<Peer>& peer,
const bsl::string& filename)
: d_peer(peer)
, d_filename(filename)
{
}
// ...
// ACCESSORS
const bsl::weak_ptr<Peer>& peer() const { return d_peer; }
const bsl::string& filename() const { return d_filename; }
};

d) A search function that takes a list of keywords and returns available results by searching the cached peers:

void search(bsl::vector<SearchResult> * /* results */,
const PeerCache& peerCache,
const bsl::vector<bsl::string>& /* keywords */)
{
for (PeerCache::PeerConstIter iter = peerCache.begin();
iter != peerCache.end();
++iter) {

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:

bsl::shared_ptr<Peer> peerSharedPtr = iter->lock();
if (peerSharedPtr) {
// Search the peer for file best matching the specified
// keywords and if a file is found add the returned
// SearchResult object to result.
// ...
}
}
}

e) A download function that downloads a file selected by the user:

void download(const SearchResult& result)
{
bsl::shared_ptr<Peer> peerSharedPtr = result.peer().lock();
if (peerSharedPtr) {
// Download the result.filename() file from peer knowing that
// the peer is still connected.
}
}

Example 6: Custom Deleters

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.

class my_MutexUnlockAndBroadcastDeleter {
// DATA
bslmt::Mutex *d_mutex_p; // mutex to lock (held, not owned)
bslmt::Condition *d_cond_p; // condition variable used to broadcast
// (held, not owned)
public:
// CREATORS
/// Create this `my_MutexUnlockAndBroadcastDeleter` object. Use
/// the specified `cond` to broadcast a signal and the specified
/// `mutex` to serialize access to `cond`. The behavior is
/// undefined unless `mutex` is not 0 and `cond` is not 0.
my_MutexUnlockAndBroadcastDeleter(bslmt::Mutex *mutex,
: d_mutex_p(mutex)
, d_cond_p(cond)
{
BSLS_ASSERT(mutex);
BSLS_ASSERT(cond);
d_mutex_p->lock();
}
my_MutexUnlockAndBroadcastDeleter(
my_MutexUnlockAndBroadcastDeleter& original)
: d_mutex_p(original.d_mutex_p)
, d_cond_p(original.d_cond_p)
{
}
Definition bslmt_condition.h:220
Definition bslmt_mutex.h:315
void lock()
Definition bslmt_mutex.h:392
#define BSLS_ASSERT(X)
Definition bsls_assert.h:1804

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.

void operator()(void *)
{
d_cond_p->broadcast();
d_mutex_p->unlock();
}
};
void broadcast()
Definition bslmt_condition.h:389
void unlock()
Definition bslmt_mutex.h:410

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.

template <class ELEMENT_TYPE>
class my_SafeQueue {
// DATA
bslmt::Mutex d_mutex;
// . . .
public:
// MANIPULATORS
void push(const ELEMENT_TYPE& obj);
ELEMENT_TYPE pop();
shared_ptr<bsl::deque<ELEMENT_TYPE> > queue();
};
template <class ELEMENT_TYPE>
void my_SafeQueue<ELEMENT_TYPE>::push(const ELEMENT_TYPE& obj)
{
d_queue.push_back(obj);
d_cond.signal();
}
template <class ELEMENT_TYPE>
ELEMENT_TYPE my_SafeQueue<ELEMENT_TYPE>::pop()
{
while (!d_queue.size()) {
d_cond.wait(&d_mutex);
}
ELEMENT_TYPE value(d_queue.front());
d_queue.pop_front();
return value;
}
template <class ELEMENT_TYPE>
shared_ptr<bsl::deque<ELEMENT_TYPE> >
my_SafeQueue<ELEMENT_TYPE>::queue()
{
return shared_ptr<bsl::deque<ELEMENT_TYPE> >(
&d_queue,
MyMutexUnlockAndBroadcastDeleter(&d_mutex, &d_cond),
0);
}
Definition bslstl_deque.h:772
Definition bslmt_lockguard.h:234

Implementation Hiding

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: Hidden Interfaces

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:

class my_Session;

Next, we define the my_SessionManager class:

class my_SessionManager {
// TYPES
// DATA
bslmt::Mutex d_mutex;
HandleMap d_handles;
int d_nextSessionId;
bslma::Allocator *d_allocator_p;

It is useful to have a designated name for the shared_ptr to my_Session:

public:
// TYPES
typedef shared_ptr<my_Session> my_Handle;

We need only a default constructor:

// CREATORS
my_SessionManager(bslma::Allocator *allocator = 0);

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.

// MANIPULATORS
my_Handle openSession(const bsl::string& sessionName);
void closeSession(my_Handle handle);
// ACCESSORS
bsl::string getSessionName(my_Handle handle) const;
};

Now, in the implementation of the code, we can define and implement the my_Session class:

class my_Session {
// DATA
bsl::string d_sessionName;
int d_handleId;
public:
// CREATORS
my_Session(const bsl::string& sessionName,
int handleId,
bslma::Allocator *basicAllocator = 0);
// ACCESSORS
int handleId() const;
const bsl::string& sessionName() const;
};
// CREATORS
inline
my_Session::my_Session(const bsl::string& sessionName,
int handleId,
bslma::Allocator *basicAllocator)
: d_sessionName(sessionName, basicAllocator)
, d_handleId(handleId)
{
}
// ACCESSORS
inline
int my_Session::handleId() const
{
return d_handleId;
}
inline
const bsl::string& my_Session::sessionName() const
{
return d_sessionName;
}

The following shows the implementation of my_SessionManager. Note that the interface for my_Session is not known:

inline
my_SessionManager::my_SessionManager(bslma::Allocator *allocator)
: d_nextSessionId(1)
, d_allocator_p(bslma::Default::allocator(allocator))
{
}
inline
my_SessionManager::my_Handle
my_SessionManager::openSession(const bsl::string& sessionName)
{
my_Handle session(new (*d_allocator_p) my_Session(sessionName,
d_nextSessionId++,
d_allocator_p));
d_handles[session->handleId()] = session;
return session;
}
inline
void my_SessionManager::closeSession(my_Handle handle)
{
HandleMap::iterator it = d_handles.find(handle->handleId());
if (it != d_handles.end()) {
d_handles.erase(it);
}
}
inline
bsl::string my_SessionManager::getSessionName(my_Handle handle) const
{
return handle->sessionName();
}
Definition balxml_encoderoptions.h:68

Example 8: Opaque Types

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:

class my_SessionManager {
// TYPES
typedef bsl::map<int, shared_ptr<void> > HandleMap;
// DATA
bslmt::Mutex d_mutex;
HandleMap d_handles;
int d_nextSessionId;
bslma::Allocator *d_allocator_p;

It is useful to have a name for the void shared_ptr handle.

public:
// TYPES
typedef shared_ptr<void> my_Handle;
// CREATORS
my_SessionManager(bslma::Allocator *allocator = 0);
// MANIPULATORS
my_Handle openSession(const bsl::string& sessionName);
void closeSession(my_Handle handle);
// ACCESSORS
bsl::string getSessionName(my_Handle handle) const;
};

Next we define the methods of my_SessionManager:

// CREATORS
inline
my_SessionManager::my_SessionManager(bslma::Allocator *allocator)
: d_nextSessionId(1)
, d_allocator_p(bslma::Default::allocator(allocator))
{
}
// MANIPULATORS
inline
my_SessionManager::my_Handle
my_SessionManager::openSession(const bsl::string& sessionName)
{

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.

my_Handle session(new (*d_allocator_p) my_Session(sessionName,
d_nextSessionId++,
d_allocator_p));
shared_ptr<my_Session> myhandle =
bslstl::SharedPtrUtil::staticCast<my_Session>(session);
d_handles[myhandle->handleId()] = session;
return session;
}
inline
void my_SessionManager::closeSession(my_Handle handle)
{

Perform a static cast from shared_ptr<void> to shared_ptr<my_Session>.

shared_ptr<my_Session> myhandle =
bslstl::SharedPtrUtil::staticCast<my_Session>(handle);

Test to make sure that the pointer is non-null before using myhandle:

if (!myhandle.get()) {
return; // RETURN
}
HandleMap::iterator it = d_handles.find(myhandle->handleId());
if (it != d_handles.end()) {
d_handles.erase(it);
}
}
bsl::string my_SessionManager::getSessionName(my_Handle handle) const
{
shared_ptr<my_Session> myhandle =
bslstl::SharedPtrUtil::staticCast<my_Session>(handle);
if (!myhandle.get()) {
return bsl::string();
} else {
return myhandle->sessionName();
}
}
basic_string< char > string
Definition bslstl_string.h:782

Macro Definition Documentation

◆ BSLSTL_SHAREDPTR_DECLARE_IF_COMPATIBLE

#define BSLSTL_SHAREDPTR_DECLARE_IF_COMPATIBLE

◆ BSLSTL_SHAREDPTR_DECLARE_IF_CONVERTIBLE

#define BSLSTL_SHAREDPTR_DECLARE_IF_CONVERTIBLE

◆ BSLSTL_SHAREDPTR_DECLARE_IF_DELETER

#define BSLSTL_SHAREDPTR_DECLARE_IF_DELETER (   FUNCTOR,
  ARGUMENT 
)

◆ BSLSTL_SHAREDPTR_DECLARE_IF_NULLPTR_DELETER

#define BSLSTL_SHAREDPTR_DECLARE_IF_NULLPTR_DELETER (   FUNCTOR)

◆ BSLSTL_SHAREDPTR_DEFINE_IF_COMPATIBLE

#define BSLSTL_SHAREDPTR_DEFINE_IF_COMPATIBLE

◆ BSLSTL_SHAREDPTR_DEFINE_IF_CONVERTIBLE

#define BSLSTL_SHAREDPTR_DEFINE_IF_CONVERTIBLE

◆ BSLSTL_SHAREDPTR_DEFINE_IF_DELETER

#define BSLSTL_SHAREDPTR_DEFINE_IF_DELETER (   FUNCTOR,
  ARGUMENT 
)

◆ BSLSTL_SHAREDPTR_DEFINE_IF_NULLPTR_DELETER

#define BSLSTL_SHAREDPTR_DEFINE_IF_NULLPTR_DELETER (   FUNCTOR)