The Apple Class Suites (ACS) library provides the CAutoPtr_AC and CRefCountingPtr_AC template classes to help you perform pointer operations in your C++ code. These lightweight classes emulate, for a subset of operations, a standard C++ or C pointer.
An auto-pointer owns the data it points to. When the auto-pointer is deleted, it deletes its data if it is appropriate to do so. In the presence of native exceptions, the use of an auto-pointer can be invaluable because, as a stack-based object, the destructor for an auto-pointer is always called. This ensures that the memory an auto-pointer points to is always deleted.
This note first demonstrates the general use of auto-pointers, then provides more detail on how to use auto-pointers with exceptions.
An auto-pointer is particularly useful as a replacement for a standard pointer data member. An auto-pointer by default sets itself to NULL, so you don't have to initialize it (although you can), and an auto-pointer automatically deletes the data it points to when it goes out of scope.
For example:
class foo { public: int data; void DoSomethingWacky(); }; class bar { public bar() // bar's constructor { fFooPointer = new foo; } CAutoPtr_AC<foo> fFooPointer; };
In this example, bar has a pointer to an instance of a foo object which is allocated and assigned to fFooPointer. fFooPointer is deleted automatically when the instance of bar goes out of scope. You don't have to worry about anything other than setting the fFooPointer in the constructor of bar.
To access data the auto-pointer points to, you use the member access operator "->":
fFooPointer->data = 4; fFooPointer->DoSomethingWacky();
You can also access the auto-pointer's member functions like this:
if(!fFooPointer.IsNULL()) // use the '.' to get at auto-pointer methods { fFooPointer->data = 4; fFooPointer->DoSomethingWacky(); }
Note that in debug versions, an ACS auto-pointer throws an exception if you attempt to dereference the pointer when it is NULL. Getting an exception instead of a crash is another excellent reason to use an auto-pointer.
You can use an auto-pointer in other familiar, pointer-like ways:
(*fFooPointer).data = 5; if(fFooPointer == anotherFooPointer) or: if(fFooPointer != anotherFooPointer)
Auto-pointers fully support dereferencing and comparison, and you can access data directly with the get method:
foo* standardPtr = fFooPointer.get();
Or you can simply use the conversion operator:
foo* standardPtr = fFooPointer;
As you can see, you can use an auto-pointer to do the same things you normally do with a pointer.
There are times when you want to use a temporary pointer to data but the code you're working with may throw an exception. For example:
foo* foofunc() { foo* tempFoo = NULL; try { foo* tempFoo = new foo; ... do stuff that may throw exception } catch(...) { if (tempFoo) { delete tempFoo; tempFoo = NULL; } } return tempFoo; }
By using an auto-pointer, you can simplify your code, avoid the use of try and catch, yet still guarantee the temporary pointer will be freed if an exception occurs:
foo* foofunc() { CAutoPtr_AC<foo> tempFoo = new foo; ... do stuff that may throw exception return tempFoo.release(); }
Calling the auto-pointer's release method tells the pointer not to delete the data it is pointing to. The release method returns a pointer to the data.
It is important to note the benefit gained by using an auto-pointer in this example. Remember that the destructor of a stack-based object is guaranteed to be called when the object goes out of scope, so tempFoo's destructor will always be called (though it will not free its data if the release method has been called), even if an exception is thrown.
There is a significant difference between assignment with standard pointers and assignment with auto-pointers. In the following code sample, both standard pointers are set to the same value and there isn't clear ownership of what they point to.
foo* fooptr = new foo; // set fooptr to point to some data (not shown) foo* otherFooPointer = fooptr; // both pointers point to the same data
When the previous code sample is converted to use auto-pointers, the code looks similar but has a different result.
CAutoPtr_AC<foo> fooptr = new foo; // set fooptr to point to some data (not shown) CAutoPtr_AC<foo> otherFooPointer = fooptr; // otherFooPointer points to the data; fooptr points to NULL
With auto-pointers based on CAutoPtr_AC, assignment denotes a change in ownership. (See the next section for information on assignment with pointers based on CRefCountingPtr.) When one auto-pointer is assigned to another, the leftmost auto-pointer (otherFooPointer) ends up owning the pointed-to data, while the auto-pointer being assigned from (fooptr) loses ownership of the data and is set to point to NULL. Ensuring a unique owner of the data prevents double deletion and other potentially nasty problems. It also means that if you need to have two pointers that point to the same data, it is best to mix in MRefCountable_AC and use a CRefCountingPtr_AC pointer (described in the next section) rather than a CAutoPtr_AC pointer.
Note that if otherFooPointer points to valid data of its own before the assignment, that data is deleted. This is done to prevent memory leaks, because otherFooPointer is the last known owner of the data.
You use CRefCountingPtr_AC to create one or more pointers to an object. The pointed-to object (the pointee) must be instantiated from a class that mixes in MRefCountable_AC, which stores a reference count that can be incremented and decremented in various ways. For example, when you create a CRefCountingPtr_AC pointer and make it point to an object, the pointee's reference count is incremented. When you delete your pointer, its constructor decrements the pointee's reference count, causing the pointee to be deleted if the count reaches zero. Ownership of a pointee is distributed among any reference counting pointers that point to it.
Assignment (shown below) increments the pointee's reference count as necessary and causes both pointers point to the same data. If the pointer being assigned to currently points to a different pointee, then that pointee's reference count is decremented (and the pointee deleted if the count reaches 0) before the assignment takes place.
// foo inherits from MRefCountable_AC CRefCountingPtr_AC<foo> fooptr = new foo; CRefCountingPtr_AC<foo> otherFooPointer = fooptr;
After this code is executed, both fooptr and otherFooPointer point to the same instance of foo, which has a reference count of two.
Using a reference counting pointer can help eliminate memory leaks because its destructor method is always called when the pointer goes out of scope. Even if an exception occurs, the reference count for the pointed-to object will be decremented, and the object will eventually be deleted
Auto-pointers have no virtual functions, so they don't require a vtbl. Auto-pointers have a single pointer as member data, so they don't take up more memory than a standard pointer. Because the class is simple and is inlined in its header file, method dispatching is very fast. Debug builds are slower due to debugging code, but that code is not included for nondebug builds. We don't anticipate any performance penalty for using auto-pointers.