Borland Online And The Cobb Group Present:


October, 1994 - Vol. 1 No. 10

C++ Programming Basics - Creating a smart pointer template class

In Automatically deallocating memory using smart pointers from last month's issue of Borland C++ Developer's Journal, we showed how you can create a smart pointer class for a specific data type. In the example, we created a smartCharPtr class. By creating smartCharPtr objects locally in a function (on the stack), we were able to demonstrate how they can automatically delete their corresponding heap objects.

Most of the time, the smart pointer classes you create for different data types will be very similar. Since classes that share common behavior are frequently good candidates for template classes, creating a template class for smart pointers is an obvious next step.

In this article, we'll move the functionality of the smartCharPtr class to a template class so we can quickly apply this design technique for other data types. In addition, we'll examine some other features you may want to add to your smart pointer classes.

Smart pointer templates

As was the case with the smartCharPtr class from last month, a smart pointer template class will allow us to create stack-based objects that control our interaction with a heap-based object. In the smartCharPtr class, our design goals were relatively simple: prevent a memory leak via the corresponding heap object by deleting that object in the smart pointer's destructor. For any of the simple types, we can use this basic design with few changes.

Simple types

To change the smartCharPtr class into a template class, you'll begin by adding

template <class T> class smartPtr

to the beginning of the class definition and then replacing the smartCharPtr type name with the class template name smartPtr. Then, you'll replace any occurrence of the char type with the template type identifier T.

Finally, you'll need to remove the string-related code from the destructor and the assignment operator (operator =). This includes the destructor code that displays the string (using cout << ptr) and deletes it, and the code in the assignment operator that assumes the ptr member is pointing to an array of characters.

At this point, you'll have a smart pointer template class that will let you enjoy the memory protection benefits of the class smartCharPtr­­but it will work correctly with individual objects only. If you wanted to manipulate dynamically allocated arrays (heap arrays) with a smart pointer class, you'd need to do some more work.

Arrays

Since you might want to use your smart pointer template class with types other than char, you'd need to make some adjustments to the smartCharPtr class member functions if you wanted to work with arrays of integers, floating point values, or other types. (This is the case because the strlen() function in the smartCharPtr class's assignment operator expects char pointers.) Unfortunately, creating a smart pointer class for arrays of objects poses some implementation problems.

For example, if you created the class smartArrayPtr, you'd need to make sure you were initializing it with a pointer to an array (since you'd want to call delete [] on the array's pointer). In addition, you'd probably want to overload operator [] to allow you to access individual elements of the array.

If our proposed smartArrayPtr class is beginning to sound like one of Borland's collection classes, you'll understand the best reason for not creating a smartArrayPtr class: Borland's already done so in its BIDS (Borland International Data Structures) classes. In addition to providing iterator functions and classes and sanity checking on array indexes (to keep you from examining data past the end of an array), the BIDS classes can also serve as very sophisticated smart pointer classes for arrays (or lists, or hash tables, and so on).

Structs and classes

Now, your smartPtr template class works correctly for individual objects, but using the smart pointers with struct or class objects is a bit awkward. For example, if you create a new smart pointer class for an employeeData struct by writing

typedef smartPtr<employeeData> smartEmpPtr;

and you then create a smartEmpPtr object named employee1, you'd need to write

(*employee1).salary

to access the salary data member of this object.

Instead, you could add an overloaded version of operator -> to the smartPtr class that would allow you to write

employee1->salary

Unfortunately, now you won't be able to use the smartPtr class for the simple types, since the syntax int number-> is meaningless and will generate a compiler error.

However, since you decided earlier not to create a smartArrayPtr class (you'll use the BIDS classes instead), and creating a smart pointer for an individual int, char, or float value would be overkill, you'll go ahead and add an overloaded version of operator -> to the smartPtr class. Now, let's put together all the pieces of our smart pointer template class.

Example

Launch the Borland C++ 3.1 DOS Integrated Development Environment (IDE). In the IDE, choose New from the File menu and enter the code from Listing A, in the data-file window that appears.


Listing A: SMRTTMPL.CPP

#include <iostream.h>
#include <string.h>

template<class T> class smartPtr
{
  T* ptr;

 public:
  smartPtr(T* p) : ptr(p) {}
  ~smartPtr()
  { delete ptr; }

  operator T*()
  { return ptr; }

  operator const T*()
  { return ptr; }

  T* operator-> ()
  { return ptr; }

  smartPtr<T>& operator= (const smartPtr<T>&);
  // Intentionally undefined - see text
  smartPtr(const smartPtr<T>&);
  // Intentionally undefined - see text
};

struct employee
{
  employee(char* n, float h) :
    hourlyRate(h) { strcpy(name, n); }
  char name[20];
  float hourlyRate;

  employee& operator= (const employee&);
};


// A global function that prints data
// from an employee object pointer

void printEmployeeData(const employee* e)
{
  cout << "The hourly rate for ";
  cout << e->name << " is $";
  cout << e->hourlyRate << endl;
}

int main()
{
  // Create a normal pointer
  employee* emp1 =
    new employee("T. Striker", 3.35);

  // Create a smart pointer
  smartPtr<employee>
    emp2(new employee("B. Shatner", 3.85));

  // Display the employee data
  printEmployeeData(emp1);
  printEmployeeData(emp2);

  delete emp1; // Delete the normal pointer
  return 0;
}

Now, choose Save from the File menu and then choose Run from the Run menu. When the IDE finishes building the application, it will shell out to DOS, run SMRTTMPL.EXE, and then return to the IDE.

To view the output of the program, choose Output from the Window menu. The output from SMRTTMPL.EXE should look like the following:

The hourly rate for T. Striker is $3.35
The hourly rate for B. Shatner is $3.85

As you can tell, the printEmployeeData() function can accept a smart pointer to an employee object as well as a traditional pointer to an employee object.

As we mentioned earlier, adding the overloaded version of operator -> prevents you from using the smartPtr class with the simple types. To see this, add the line

smartPtr<int> int1(new int);

anywhere inside the main() function. Now if you rebuild the project, you'll see the error

operator -> must return a pointer or a class

appear for the body of the operator -> () function. Remove the line that creates a smartPtr<int> object to clear the error.

Going further

The smart pointer template class we've presented here is by no means complete. In fact, you'll want to consider a number of possible refinements to this template class before implementing it in your programs.

Null pointer detection

One of the most common uses for smart pointers is detecting the use of null pointers. If you add assert(ptr); in any of the smart pointer class's functions that return the ptr member (and you haven't defined the NDEBUG constant), your program will halt when you try to use a smart pointer that's pointing to a 0 address.

Multiple deletions

In the example from last month's article, there's a potential bug waiting for you. Consider what would happen if you were to create a second smart pointer and initialize it with the first.

By default, a C++ compiler will create a copy constructor and assignment operator for a class if you don't provide them (we haven't). According to the C++ language guidelines, a compiler-generated copy constructor or assignment operator will perform a member-by-member copy from the first object to the second.

At first, this behavior may not seem to pose a threat to your program. However, if your program has two smart pointers whose internal pointers represent the same memory address (and both smart pointers are in the same scope), both smart pointers will try to delete the same block of memory when the program destroys them.

There are a couple of solutions to this. The first solution (and the easiest) is to prevent assignment between two smart pointers or initialization of one smart pointer with another. In the example code, we've done this by declaring overloaded versions of the assignment operator (operator =) and the copy constructor (smartPtr(const smartPtr<T>&)) but never defining them. See Initialization versus assignment for more information. Unfortunately, this is an incomplete solution because it doesn't prevent you from initializing two new smart pointers with the same address.

To solve this problem, you'll need to use the second solution: reference counting. Reference counting is the act of monitoring how many smart pointers are using a particular block of memory. In the destructor of the smart pointer, the smart pointer checks to see if it's the last one using the memory block; if it is, it deletes the block. In a future issue, we'll look at some ways you can implement reference counting.

Conclusion

A smart pointer template class lets you enjoy the benefits of smart pointers without having to rewrite the class for each new pointer class. By overloading operator -> in your smart pointer template class, you can pass smart pointers to many functions that expect normal pointers, but still take advantage of a smart pointer's ability to automatically clean up its heap memory.

Thanks to Cay S. Horstmann of San Jose State University for inspiring this article.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.