BDE 4.14.0 Production release
|
Provide a managed pointer class.
ManagedPtr
-related utility functionsThis component provides a proctor, bslma::ManagedPtr
, similar to bsl::auto_ptr
, that supports user-specified deleters. The proctor is responsible for the automatic destruction of the object referenced by the managed pointer. As a "smart pointer", this object offers an interface similar to a native pointer, supporting dereference operators (*, ->), (in)equality comparison and testing as if it were a boolean value. However, like bsl::auto_ptr
it has unusual "copy-semantics" that transfer ownership of the managed object, rather than making a copy. It should be noted that this signature does not satisfy the requirements for an element-type stored in any of the standard library containers. Note that this component will fail to compile when instantiated for a class that gives a false-positive for the type trait bslmf::IsPolymorphic
. See the bslmf_ispolymporphic component for more details.
This component also provides the bslma::ManagedPtrUtil
struct
, which defines a namespace for utility functions that facilitate working with ManagedPtr
objects. Of particular note are the allocateManaged
and makeManaged
class methods that can be used to create a managed object as well as a ManagedPtr
to manage it, with the latter being returned. allocateManaged
takes a bslma::Allocator *
argument that is both (1) used to allocate the footprint of the managed object and (2) used by the managed object itself if it defines the bslma::UsesBslmaAllocator
trait. makeManaged
does not take a bslma::Allocator *
argument and uses the default allocator to allocate the footprint of the managed object instead.
An object that will be managed by a ManagedPtr
object is typically dynamically allocated and destroyed by a factory. For the purposes of this, component, a factory is any class that provides a deleteObject
function taking a single argument of the (pointer) type of the managed pointer. The following 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.
Note that when the ManagedPtr(MANAGED_TYPE *)
constructor is used, the managed object will be destroyed with a built-in deleter that calls delete ptr
, but when the ManagedPtr(MANAGED_TYPE *, FACTORY_TYPE *)
constructor is called with 0 == factory
, the currently installed default allocator will be used as the factory.
When a managed pointer is destroyed, the managed object is destroyed using the user supplied "deleter". A deleter is simply a function that is invoked with two void *
arguments: a pointer to the object to be destroyed, and a pointer to a cookie
that is supplied at the same time as the deleter
and managed object.
The meaning of the cookie
depends on the specific deleter. Typically a deleter function will accept the two void *
pointers and internally cast them to the appropriate types for pointers to the managed object and cookie
. Note that there are no methods taking just a deleter, as the user must always supply a cookie
to be passed when the deleter is actually invoked.
Note that this component still supports (deprecated) legacy deleters that expect to be passed pointers to the specific cookie
and managed object types in use. This latter form of deleter was deprecated as it relies on undefined behavior, casting such function pointers to the correct form (taking two void *
arguments) and invoking the function with two void *
pointer arguments. While this is undefined behavior, it is known to have the desired effect on all platforms currently in use.
In a managed pointer, the pointer value (the value returned by the get
method) and the pointer to the managed object need not have the same value. The loadAlias
method allows a managed pointer to be created as an "alias" to another managed pointer (possibly of a different type), which we'll call the "original" managed pointer. When get
is invoked on the alias, the aliased pointer value is returned, but when the managed pointer is destroyed, the original managed object will be passed to the deleter. (See also the documentation of the alias
constructor or of the loadAlias
method.)
The principal usage of a managed pointer is to guarantee that a local object will be deallocated properly should an operation throw after its allocation. In this, it is very similar to bsl::auto_ptr
. It is required for the proper functioning of this component that a deleter does not throw at invocation (upon destruction or re-assignment of the managed pointer).
ManagedPtr
objects can be implicitly and explicitly cast to different types in the same way that native pointers can.
Through "aliasing", a managed pointer of any type can be explicitly cast to a managed pointer of any other type using any legal cast expression. See example 4 on type casting
below for more details.
As with native pointers, a managed pointer of the type B
that is derived from the type A
, can be directly assigned to a ManagedPtr
of A
. Likewise a managed pointer of type B
can be directly assigned to a ManagedPtr
of const B
. However, the rules for construction are a little more subtle, and apply when passing a bslma::ManagedPtr
by value into a function, or returning as the result of a function.
Note that std::auto_ptr
has the same restriction, and this failure will occur only on compilers that strictly conform to the C++ standard, such as recent gcc compilers or (in this case) IBM xlC.
In this section we show intended usage of this component.
We demonstrate using ManagedPtr
to configure and return a managed object implementing an abstract protocol.
First we define our protocol, Shape
, a type of object that knows how to compute its area
. Note that for expository reasons only, we do not give Shape
a virtual destructor.
Then we define a couple of classes that implement the Shape
protocol, a Circle
and a Square
.
Next we implement the methods for Circle
and Square
.
Then we define an enumeration that lists each implementation of the Shape
protocol.
Now we can define a function that will return a Circle
object or a Square
object according to the specified kind
parameter, and having its dimension
specified by the caller.
Then, we can use our function to create shapes of different kinds, and check that they report the correct area. Note that we are using a radius of 1.0
for the Circle
and integral side-length for the Square
to support an accurate operator==
with floating-point quantities. Also note that, despite the destructor for Shape
being non-virtual, the correct destructor for the appropriate concrete Shape
type is called. This is because the destructor is captured when the ManagedPtr
constructor is called, and has access to the complete type of each shape object.
Next, we observe that as we are creating objects dynamically, we should pass an allocator to the makeShape
function, rather than simply accepting the default allocator each time. Note that when we do this, we pass the user's allocator to the ManagedPtr
object as the "factory".
Finally we repeat the earlier test, additionally passing a test allocator:
Suppose that we wish to give access to an item in a temporary array via a pointer, which we will call the "finger". The finger is the only pointer to the array or any part of the array, but the entire array must be valid until the finger is destroyed, at which time the entire array must be deleted. We handle this situation by first creating a managed pointer to the entire array, then creating an alias of that pointer for the finger. The finger takes ownership of the array instance, and when the finger is destroyed, it is the array's address, rather than the finger, that is passed to the deleter.
First, let's say our array stores data acquired from a ticker plant accessible by a global getQuote
function:
Then, we want to find the first quote larger than a specified threshold, but would also like to keep the earlier and later quotes for possible examination. Our getFirstQuoteLargerThan
function must allocate memory for an array of quotes (the threshold and its neighbors). It thus returns a managed pointer to the desired value:
Next, we allocate our array with extra room to mark the beginning and end with a special END_QUOTE
value:
Then, we create a managed pointer to the entire array:
Next, we read quotes until the array is full, keeping track of the first quote that exceeds the threshold.
Now, we use the alias constructor to create a managed pointer that points to the desired value (the finger) but manages the entire array:
Then, our main program calls getFirstQuoteLargerThan
like this:
Next, we also print the preceding 5 quotes in last-to-first order:
Then, to move the finger, e.g., to the last position printed, one must be careful to retain the ownership of the entire array. Using the statement result.load(result.get()-i)
would be an error, because it would first compute the pointer value result.get()-i
of the argument, then release the entire array before starting to manage what has now become an invalid pointer. Instead, result
must retain its ownership to the entire array, which can be attained by:
Finally, if we reset the result pointer, the entire array is deallocated:
Suppose we want to track the number of objects currently managed by ManagedPtr
objects.
First we define a factory type that holds an allocator and a usage-counter. Note that such a type cannot sensibly be copied, as the notion count
becomes confused.
Next, we provide the createObject
and deleteObject
functions that are standard for factory objects. Note that the deleteObject
function signature has the form required by bslma::ManagedPtr
for a factory.
Then, we round out the class with the ability to query the count
of currently allocated objects.
Next, we define the operations declared by the class.
Then, we can create a test function to illustrate how such a factory would be used with ManagedPtr
.
Next, we declare a test allocator, and an object of our CountedFactory
type using that allocator.
Then, we open a new local scope and declare an array of managed pointers. We need a local scope in order to observe the behavior of the destructors at end of the scope, and use an array as an easy way to count more than one object.
Next, we load each managed pointer in the array with a new int
using our factory cf
and assert that the factory count
is correct after each new int
is created.
Then, we reset
the contents of a single managed pointer in the array, and assert that the factory count
is appropriately reduced.
Next, we load
a managed pointer with another new int
value, again using cf
as the factory, and assert that the count
of valid objects remains the same (destroy one object and add another).
Finally, we allow the array of managed pointers to go out of scope and confirm that when all managed objects are destroyed, the factory count
falls to zero, and does not overshoot.
ManagedPtr
objects can be implicitly and explicitly cast to different types in the same way that native pointers can.
As with native pointers, a pointer of the type B
that is publicly derived from the type A
, can be directly assigned a ManagedPtr
of A
.
First, consider the following code snippets:
If the statements:
are legal expressions, then the statements
Through "aliasing", a managed pointer of any type can be explicitly converted to a managed pointer of any other type using any legal cast expression. For example, to static-cast a managed pointer of type A to a managed pointer of type B, one can simply do the following:
or even use the less safe "C"-style casts:
Note that when using dynamic cast, if the cast fails, the target managed pointer will be reset to an unset state, and the source will not be modified. Consider for example the following snippet of code:
If the value of aPtr
can be dynamically cast to B *
then ownership is transferred to bPtr
; otherwise, aPtr
is to be modified. As previously stated, the managed object will be destroyed correctly regardless of how it is cast.
Suppose we want to allocate memory for an object, construct it in place, and obtain a managed pointer referring to this object. This can be done in one step using two free functions provided in bslma::ManagedPtrUtil
.
First, we create a simple class clearly showing the features of these functions. Note that this class does not define the bslma::UsesBslmaAllocator
trait. It is done intentionally for illustration purposes only, and definitely is not recommended in production code. The class has an elided interface (i.e., copy constructor and copy-assignment operator are not included for brevity):
Next, we create a code fragment that will construct a managed String
object using the default allocator to supply memory:
Suppose we want to have a different allocator supply memory allocated by the object:
Then, create a string to copy:
Next, dynamically create an object and obtain the managed pointer referring to it using the bslma::ManagedPtrUtil::makeManaged
function:
Note that memory for the object itself is supplied by the default allocator, while memory for the copy of the passed string is supplied by another allocator:
Then, make sure that all allocated memory is successfully released after managed pointer destruction:
If you want to use an allocator other than the default allocator, then the allocateManaged
function should be used instead:
Next, let's look at a more common scenario where the object's type uses bslma
allocators. In that case allocateManaged
implicitly passes the supplied allocator to the object's constructor as an extra argument in the final position.
The second example class almost completely repeats the first one, except that it explicitly defines the bslma::UsesBslmaAllocator
trait:
Then, let's create two managed objects using both makeManaged
and allocateManaged
:
Note that we need to explicitly supply the allocator's address to makeManaged
to be passed to the object's constructor:
But the supplied allocator is implicitly passed to the constructor by allocateManaged
:
Finally, make sure that all allocated memory is successfully released after the managed pointers (and the objects they manage) are destroyed: