Click Here!
home account info subscribe login search FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
[an error occurred while processing this directive]
Previous Table of Contents Next


Steps

1.  Start by creating a header file to contain your class definition. I called the example classm.hpp, with the file extension denoting that this is a C++ header file.
2.  Define your class. The example uses a template class to provide type safety in addition to being able to use things such as overloaded assignment functions internally. The following listing shows the definition of a memory management class from classm.hpp:
template <class t>
class MemClass
{
   private :
   t    *_data ;
   int     array ;

   public:

   MemClass() : array(0) {} ;
   MemClass( t *data, int numitems ) ;

   // this constructor assumes the contained class’s assigment
   // operator is overloaded.
   MemClass( const MemClass<t> &copyfrom ) ;

   // data access functions

   t*    GetPtr() {    return _data ; }

   MemClass<t>& operator=( const MemClass<t>& copyfrom ) ;

   ~MemClass() ;
} ;

Note again that even inline functions are not implemented within the class definition to make this more readable. This is an especially important point to remember if others have to read your code.
3.  Implement the constructors and destructor. As a general rule, a copy constructor should always be defined to prevent little problems from cropping up. It’s possible that some STL or library function might cause the copy constructor to be called, and in this case, the default would do exactly what you don’t want it to. The following listing shows the constructors and destructor for the sample memory management class from classm.hpp:
template <class t>
inline
MemClass<t>::MemClass( t *data, int numitems = 1 )
{
    _data = data ;
   array = numitems ;
}

template <class t>
MemClass<t>::~MemClass()
{
    if( array == 1 )
       delete _data ;
   else
       delete[] _data ;
}

template <class t>
MemClass<t>::MemClass( const MemClass<t> &copyfrom )
{

   array = copyfrom.array ;

   if( copyfrom.array == 1 )
   {
      _data = new t ;
      *_data = *copyfrom._data ;
    }
    else
   {
       _data = new t[array] ;
	for ( int i = 0; i < array ; i++ )
	  _data[i] = copyfrom._data[i] ;
   }
}

Note how the class attempts to handle arrays. The class has no way of finding out (within the scope of the language anyway) whether the pointer it is given points to an array or to a single object. The second argument to the constructor allows the user of the class to indicate whether it is to manage an array.
The array size given needs to be accurate only if you are going to copy the class with the assignment operator or copy constructor. Otherwise, it is used only to tell which version of delete to call.
4.  Implement a data access function. In this case, I chose not to use an overloaded operator because it is quite possible that the address-of operator will be used to deal with objects of this class for things such as function parameters. It is only one line, and so it is implemented in the class body:
t*    GetPtr() {    return _data ; }
5.  Implement other functionality for the class. This is really up to the designer of the class (you). In this case, an assignment operator is normally very useful, and so this is provided. Plenty of things could also be added, such as a release function that deletes the contained object, which can be useful for handling objects that use a large amount of memory. This listing shows the overloaded assignment operator from classm.hpp:
template <class t>
MemClass<t>& MemClass<t>::operator=( const MemClass<t>
				     ⇒& copyfrom )
{
   if( &copyfrom == this ) // deal with possible self-assignment
       return *this ;

   if ( array == 1 )
      delete _data ;
   else if ( array > 1 )
       delete[] _data ;

   array = copyfrom.array ;

   if( copyfrom.array == 1 )
   {
       _data = new t ;
      *_data = *copyfrom._data ;
   }
   else
   {
       _data = new t[array] ;

      for( int i = 0 ; i < array ; i++ )
	  _data[i] = copyfrom._data[i] ;
   }
   return *this ;
}

Note how self-assignment is handled in this operator. This is critical because assigning an object to itself is something that could possibly occur inside a library function, and not handling this possibility could be disastrous.

How It Works

The class essentially assumes ownership of a pointer to a class, and deallocates it when it falls out of scope. It also deals with one other problem that dealing with pointers creates: copying pointers by copying the object to which the pointer points. This eliminates program errors caused by forgetting to delete a pointer before assigning another value to it.

Comments

The Standard Template Library also includes a class that works like this one, called auto_ptr.

13.3 Make an object that deallocates itself when there is no more code referencing it?

Problem

Programs that do not execute in an absolutely linear fashion can run into problems when one thread of execution tries to access an object that has already fallen out of scope in the thread that allocated it.

Operating system callback functions (in which the operating system calls a specified entry point in a user program, usually asynchronously) have similar problems to threads in that they might be accessing data deallocated by the program.

Operating systems provide shared data areas that handle this automatically. However, these operating system features usually are designed for C, not C++, so destructors are not called.

Technique

A great number of things relating to how threads share and lock memory are operating system–dependent. For the purpose of this How-To, code that is OS-dependent will be left out and replaced with comments indicating any issues with the code.

Because this is code that can quickly become complicated, it is critical that the class is designed (and viewed by the reader to be designed) to solve very specific problems.

Specifically, there are some issues specific to both multithreaded programming and programs that use OS callbacks: Multiple threads can reference the object at the same time. With OS callback functions, the reference count might get to zero before the callback function is executed. This means that handling an OS callback might require special rules for handling the contained data (such as deleting the contained object the second time the reference count arrives at zero). On the other hand, whether the callback function can execute at the same time as other code in the same program is operating system–dependent. Some (non-preemptive multitasking) operating systems will only execute a callback while a program is executing an API call to the system (for example, Windows 3.1 during the GetMessage() API call).

The memory management classes cannot take care of all thread-dependent issues. Much of the responsibility falls on the actual data that’s being handled. This gives the code the most flexibility because the only rule that could really be enforced by a container class is to prevent more than one thread from using the contained data. Implementing this strategy is likely to be less efficient than letting the data class care for itself. All the container class needs to worry about is the protection of its own data used to manage the data class.


Previous Table of Contents Next


Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-1999 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permision of EarthWeb is prohibited.