Alan Wagner-Krankel has his bachelors degree in computer science from the University of Texas, and currently works for a firm developing software in C++ for electrical engineering. He welcomes questions and comments at 1017 S. Bowie Drive, Abilene, Tx, 79605. Daytime phone is (915) 695-1642.
Consider what happens when you set a pointer to some memory allocated from the heap or free store, but forget to free the memory before the pointer goes out of scope. The block becomes inaccessible, and the pointer just sits there, doing nothing, while your program runs out of memory.
With C++, it is possible to program a pointer to recognize itself as the last reference to a block when going out of scope, and then to free the space. A C++ object called a smart pointer adds this and other capabilities to pointers by doing some work in addition to storing an address.
This article describes a set of smart pointer classes that maintain a count of the number of pointers which reference an item. The RefCntPtr classes use the reference count to provide memory management that frees unused memory, and avoids more complex memory-maintenance problems. The RefCntPtr classes are also used to build a simple database buffer, where the pointers help maintain a shared pool of memory.
Functionality
The RefCntPtr classes do three basic things: they act like pointers, maintain the reference count, and free unused memory.
Acting like Pointers
The RefCntPtr classes act like pointers through operator overloading. The overloaded operators work on RefCntPtrs in a way similar to normal pointers.The most important of these operators is the class/structure member-access operator ->. If X is a smart pointer to a class, and Y is a normal pointer, both X->member and Y->member should refer to the same thing. Fortunately, in C++ the -> operator is specifically defined to do this. X->member is interpreted as (X.operator->())->member. By defining operator->() to return a normal pointer, we get the exact same result as Y->member.
Another necessary operator is the pointer dereference operator *. Again, if X is a smart pointer to a class, and Y is a normal pointer, *X and *Y should both refer to the class. To accomplish this, operator *() is defined to return a reference (&) to the item pointed to by the RefCntPtr.
To allow testing the pointer in conditional statements, operator int() is defined to return 0 if the pointer is 0, and 1 if it is not. Thus, tests such as while(ptr) and if(!ptr) can be used.
There are other operators which can be defined for smart pointers in general, but which are not useful for RefCntPtr. The subscripting operator, [], is one. For a smart pointer X, X[0] could be defined to be the same as *X, as it is with normal pointers. However, X[1] has no meaning for the RefCntPtr, so subscripting is not defined here. Similarly, the increment and decrement operators ++ and -- can be used with some smart pointers, but do not make sense here.
Maintaining Reference Count
The RefCntPtr point to instances of classes derived from RefCntItem (see Listing 1) , and the counts are stored within the instances. RefCntPtr uses the incRefCnt and decRefCnt functions to change the counts.Other functions keep the reference counts updated by the operations that change what the pointers are referencing. Thus, some of the constructors increment the count of the item that the pointer is now referencing, and the destructor decrements the reference count. The assignment operators decrement the count of the old item, copy the address, and increment the count of the new item.
The reference count can be stored somewhere other than within the item, if that is a problem. Gorlen et al. (1990, p. 361) describes the classes needed for such a scheme.
Freeing Unused Memory
When the reference count of an instance reaches 0, no RefCntPtr point at that item. The item, now unused, can be deleted, thus returning its memory to the free store.
The Code
Listing 2, refptr.hpp, contains macros for the declaration and definition of RefCntPtr. The macros help solve a problem with these pointers - a separate class must be defined for every type that needs a smart pointer. These smart pointer classes differ only in their own name and the name of the class upon which they act. Rather than copying an existing class and changing the names with an editor, these macros let the preprocessor change the names.These classes could be implemented with class templates, as described in Stroustrup and Ellis (1990). If you have access to a compiler that implements templates, you can replace these macros that only simulate templates with the real thing.
To declare a RefCntPtr class for class ABCD, the macro call RefCntPtrDECLARE(ABCD) should be placed in a header file. The macro call RefCntPtrDEFINE(ABCD) should be put in a corresponding source file. These macros will expand to the class declaration and definition. The resulting class is described throughout the program as RefCntPtr(ABCD) (see Listing 3, Listing 4, and Listing 5, anyclass.hpp, anyclass.cpp, and main1.cpp).
Listing 5, main1.cpp, contains a small test program which shows how the RefCntPtr works. Note that delete is never called in main. Instead, RefCntPtr handles garbage collection.
All of the code was tested with Borland C++ v2.0. There should not be anything compiler-specific in it.
A Simple Database Buffer
Listing 6, main2.cpp, keeps all records in memory in a pool. When the program makes a request for a record, it receives a RefCntPtr to that record. If the record is not currently in the pool, it is fetched from the disk. If there is not enough space in the pool, a candidate for removal is found by checking the reference counts, and removing a record which does not have any outstanding pointers.The use of RefCntPtr has simplified communication between the function getRecWithNew and the rest of the world. The function can allocate space with new, and then let the smart pointer take care of deleting it. The function no longer depends on an error-prone outsider deleting things.
The fact that a program must use RefCntPtr exclusively presents a potential problem. If you use normal pointers also, the reference counts will not be correct.
Extending the Buffer
The simple buffering scheme can be extended to provide more involved memory-management capabilities. In this version, records are swapped in and out of memory only when a request for a record is made. Consider what becomes possible if the smart pointer could swap the records. Then, when data is accessed through the -> and * operators, the smart pointer could check to see if the record is in memory and read it in, if necessary.One way to implement this would be to have an extra layer of indirection, with pointers similar to RefCntPtr. These pointers would reference a class which maintains a pointer to the actual record and information on how to retrieve that record, if necessary. The external user would not see this internal class.
The result would be something very close to virtual memory, with records paged in on demand. Like any good memory-management scheme, it would be almost transparent to the user outside of the buffer handler.
Other Smart Pointer Possibilities
Smart pointers are not limited to buffering schemes and garbage collection. They could also manage:
Smart pointers are yet another example of the encapsulation that C++ allows and encourages. They can be an extremely useful addition to a programmer's bag of tricks.
- Usage counts Every time an item is accessed, the pointer could increment a usage count for that item. The resulting data could be used to eliminate items that are never used, and for other tuning.
- Pointer comparisons In segmented addressing, such as on Intel/MS-DOS machines, pointer comparisons do not always work as expected. The comparison operators could be overloaded to normalize the pointers before comparing them.
- Debugging The RefCntPtr already prints a message if a zero pointer is used. This simple debugging could be extended to include range checking, and printing out addresses and other information whenever an item is accessed.
- Arrays The subscripting, increment, and decrement operators could be used to help build self-describing arrays with bounds checking.
- Other reference count problems Such as list representations.
References
Gorlen, Keith, Sanford Orlow, and Perry Plexico. 1990. Data Abstraction and Object-Oriented Programming in C++. John Wiley & Sons, New York, NY.Stroustrup, Bjarne and Margaret Ellis. 1990. The Annotated C++ Reference Manual, Addison-Wesley. Reading, MA.