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