Pointers, References and Values by Michael D. Crawford Continued...

Reference Counted Smart Pointers

Reference counted pointers provide easy, efficient memory management
with copying semantics that allow use in Standard Template Library containers.

There is a problem with the auto_ptr template. It necessarily has unsatisfying copy and assignment semantics. These surprise many programmers, and prevent its use in Standard Template Library containers, as well as preventing most classes that have auto_ptr members from being used in STL.

The problem is that auto_ptr owns the object whose pointer it holds, and must delete it when the auto_ptr is destroyed. The normal way to write both copy constructors and assignment operators (and the way C++ will do it for you if you do not provide them explicitly) is to simply copy or assign each member in sequence. But this will not work for auto_ptr because two of them would then own the object. The second deletion would corrupt the heap and cause a crash.

The compromise that has been arrived at is for the recipient auto_ptr to take ownership of the object if the auto_ptr is copied or assigned. This gives error-free behaviour, but it may not be what you want.

In particular, the Standard Template Library uses copy construction extensively and will do whacky things if the elements of its containers are auto_ptrs. One unfortunate consequence of this is that you cannot correctly use auto_ptr to implement STL containers of polymorphic objects.

It also does not work to enable polymorphism with STL containers of naked pointers, if the containers will own the pointers. You might attempt to subclass an STL container and free the pointers in your subclass' destructor but this is incorrect also, because STL containers do not have virtual destructors. If the container is destroyed by calling delete on a pointer to the base class, the subclass' destructor will not be called. Bad things will happen.

I pointed out earlier that one should strive for the bodies of both constructors and destructors to be empty. If a class holds auto_ptr members, one must write a custom copy constructor and assignment operator for it that deals with the auto_ptrs in some appropriate way. The solution could even be to pass ownership to the recipient (but you should be explicit about it), or to use new to copy construct a pointer to a newly allocated object, or if the pointer is used polymorphically, you could provide a virtual Clone() method in the base class with overrides in each subclass that return a newly allocated object of the subclass' type. You could also set the recipient pointer to NULL.

If you do not plan for the class' object to be copied or assigned, you should declare the copy constructor and assignment operator in the private section of the class declaration and omit any implementation. Then any attempt to copy or assign the objects will result in a compile error. It is important to do this rather than simply omit the declarations because copying or assignment may occur accidentally (perhaps by a future programmer who does not understand the requirements, or yourself if you forget) and the defaults that C++ will provide without your knowledge will likely cause trouble.

Each of these solutions will work in particular cases but none of them solve the STL problem. Duplicating a pointer's object can also use a lot of memory and be time-consuming. Perhaps what you would prefer to do is share a single allocated object among all the users of it, but delete it when the last user is destroyed. You can do this with reference counting. Reference counted pointers are compatible with STL, and can save memory and improve performance as well.

Reference counting is commonly done in combination with classes that override the "*" and "->" operators to provide pointer semantics, giving us the reference counted smart pointer. (But you can use reference counting in any class that manages a shared resource, not just smart pointers.)

When the object that is being managed is created, its reference count is set to one. When the managing object - the smart pointer - is copied or assigned, the reference count is incremented. When a smart pointer is destructed, as when a member variable's owning object is destructed, or a value goes out of scope, or an allocated smart pointer is deleted, the reference count is decremented. Thus copying, assignment and most destructions of reference counting memory managers are very lightweight operations. Only one underlying managed object exists the whole time so reference counters are very sparing in their use of memory.

When the reference count reaches zero the object being managed is deleted.

Besides being compatible with STL, reference counted smart pointers share the desirable property with auto_ptr of being exception safe. If you instantiate a single reference counted smart pointer, the object it manages will be deleted when it goes out of scope, however that may occur. But they have the advantage that you can pass them around more freely without worrying about weird things happening.

It is important to understand that each reference counted smart pointer shares ownership of the underlying object equally. This will cause problems if one user wants to mutate the object while another needs it to stay unchanged. You may still need to copy the underlying memory.

It is possible to implement copy on write object managers that will clone a shared object in the event a mutating operation is called on it. That is done by the ZDCPixMap graphics buffer class in the ZooLib cross-platform application framework, written by Andy Green of The Electric Magic Company. As long as you mutate a ZDCPixMap through its function call interface (rather than getting a raw pointer to its data and changing it directly), ZooLib will duplicate the underlying graphic data before it is modified, so that other users of it still see the unchanged image.

There are many variations of reference counted smart pointers available, each created to serve different needs.

For example, there is the question of where to keep the reference count. If a separate reference count is allocated with new when the first smart pointer is created, you can reference count any object without it having to cooperate - notably, you can reference count an incomplete type. On the other hand, if the reference count is kept in the object being managed, usually by deriving its class from a mixin base class, the smart pointers are smaller and quicker to create, and you avoid the overhead of the allocation and deletion of the reference count. This can be important if you create and delete new managed objects frequently.

Some smart pointers can only be used on single-threaded code. But they are fast because they have no locking overhead. The Boost library's shared_ptr is a very well designed, highly portable and easy to use single-threaded reference counted smart pointer. (It is in boost/smart_ptr.h in the Boost file download).

Other smart pointers use atomic operations to manipulate their reference count so they can be used in a multithreaded way. The atomic operation can be ensured by using an operating system locking primitive like a mutex, or you can use a small amount of assembly code to take advantage of atomic arithmetic operations provided by the microprocessor and avoid the need for system calls. Smart pointers that use the processor's atomic operations enable code to be very fast on symmetric multiprocessing systems, and very responsive even on machines with a single CPU.

One such thread-safe reference counted smart pointer is Andy Green's ZRef template. I describe ZRef in The ZooLib Cookbook. ZRef is very efficient (it also stores the reference count in the managed object, through the ZRefCounted mixin), but it comes at the cost of some difficulty in porting ZooLib to new processors and compilers, as some very careful assembly code has to be written.

Writing your own reference counted smart pointer could be an interesting and instructive exercise, but writing a thread-safe one is a very advanced problem. Andy and I are confident of ZRef's correctness, but despite the fact that Andy is a masterful programmer, it took him quite a while to get the last bugs worked out of ZRef. I have seen other "thread-safe" smart pointers used in production code that I was pretty sure had race conditions that would result in the managed object being deleted occasionally while it was still in use.

Smart pointers can provide error checking to ease debugging, such as asserting that the pointers they hold are not NULL when they are dereferenced.

Modern C++ Design cover

Modern C++ Design
Generic Programming and Design Patterns Applied

by Andrei Alexandrescu

[ Buy]  

It is possible to write smart pointers that are readily adaptible to different needs. Andrei Alexandrescu writes of policy based templates in his amazing book Modern C++ Design, in which a class such as a smart pointer is a template that takes several template parameters, one for the type to manage and the rest to implement various policies, such as whether to be single or multithreaded, where to store the reference count and so on. He provides an open source library called Loki that is discussed in the book and some policy based classes such as smart pointers and a few design patterns.

Andrei has been mentoring a study of Modern C++ Design on the ACCU Mentored Developers list as I write this update (May 2002) - a good reason for you to join the ACCU!

Sadly, Loki makes such novel use of ISO Standard C++ that many of the compilers still in use today cannot build it. An important example is Microsoft Visual C++ 6. Visual C++ 7 comes closer but I understand is not quite there yet. Andrei himself used Metrowerks CodeWarrior for Windows (which is my favorite compiler) and Comeau C++ - both these compilers are well known for excellent ISO Standard compliance. (Even more sadly, the Microsoft-supplied Windows header files cannot be compiled by an ISO compliant compiler, so each compiler for the Windows platform has special "Windows compatibility" modes that break compliance and allow you to compile incorrect code you have written yourself.)

Finally I should mention another smart pointer that is not reference counted, but meant for use where you could use auto_ptr without needing copying or assignment. Also included in the Boost smart_ptr.h is boost::scoped_ptr, which is a simple smart pointer that cannot be copied or assigned. I find it most useful to simply guarantee a pointer will be deleted when it exits the basic block where it was allocated. Not allowing assignment or copying prevents the occasional weird bug where you try one of them accidentally. The class is very lightweight.

So with the addition of scoped_ptr to our arsenal of smart pointers, in reality you should not use auto_ptr at all! Either use one of the reference counted smart pointers or scoped_ptr. The remaining reason to use auto_ptr is that one does not want to have any dependence at all on external libraries - the C++ ISO Standard does not yet provide for reference counted smart pointers, so auto_ptr is all that you can count on having in your development system.

next button previous page contents all programming tips titles

Copyright © 2000, 2001, 2002, 2005 Michael D. Crawford. All Rights Reserved.

One Must Not Trifle With Wizards For It Makes Us Soggy And Hard To Light