|
 |
 |
[an error occurred while processing this directive]
- 1. Create a header file to contain the class definition and the inline code for your class. This example uses memclass.hpp. The .hpp extension denotes a C++ header file, just to make keeping it apart from the C header files easier.
- 2. Set up defines to prevent the header from being included in a program more than once. This is vital because this header file might be included in several other header files that will finally be included in an actual .cpp file containing code. At best, the compiler will spit out an error message to the effect of multiple definitions for the same symbol, and at worst, your program will misbehave.
Enclosing the contents of your header files in pre-processor directives similar to the following will prevent errors like multiple definitions for or multiple symbols. Make sure to change the names for the #defines!
#ifndef __MEMCLASS_HPP
#define __MEMCLASS_HPP
// header file contents go here ...
#endif
- 3. Depending on the amount of allocating and deallocating memory your programs do, memory management can consume a substantial amount of your programs runtime. My advice: Keep it simple and keep it lean. Statistics are nice, but dont generally have a place in production quality code.
The following listing shows a simple pointer management class design to do this:
class MemPtr
{
private:
void* data ;
unsigned int msize ;
public:
// constructor list
MemPtr( const unsigned int size ) ;
MemPtr( const MemPtr &copyfrom ) ;
~MemPtr() ;
// information functions
unsigned int getsize() const { return msize ; }
// the allocated storage is accessed using the
// address of operator (&)
void* operator& () ;
};
The preceding code is a pretty minimalist implementation, but its relatively easy to extend it to provide for resizing and joining memory regions, for example.
- 4. Implement the shorter functions as inline. I tend to try to inline anything thats fewer than five statements and is used often in a program. Inlining functions that are too large tend to make your programs bloated.
There is another point: Even if you are inlining functions, it is best to separate the class member function definitions from their implementations to make the actual header file more readable. Consider data-access functions to be an exception if they are only one line. Larger functions are marked as inlineable by using the keyword inline. The following code snippet shows how implementation of inline functions goes in the header files from memclass.hpp:
inline
MemPtr::MemPtr( const unsigned int size )
{
msize = size ;
data = new char[size] ;
}
inline
MemPtr::MemPtr( const MemPtr &copyfrom )
{
// this contructor copies over the data area
msize = copyfrom.msize ;
data = new char[copyfrom.msize] ;
memcpy( data, copyfrom.data, msize ) ;
}
inline
void* MemPtr::operator&()
{
return data ;
}
inline
MemPtr::~MemPtr()
{
// with older C++ compilers (those that do not support
// exceptions) it may be necessary to check to see if data is
// NULL before calling delete.
delete[] data ;
}
The core of the class is the overloading of the C++ address-of operator. Instead of the default behavior of returning the pointer to the actual object, it returns the pointer to the memory region it manages.
The only other remarkable function is the destructor. Definitions have changed for the new and delete operators. delete is now smarter in that it will not deallocate memory that has already been deallocated. In previous versions, this behavior was undefined. Likewise, new no longer returns null if it fails, it now throws an xalloc exception. (Refer to Chapter 11, Exception Handling in C++, for a detailed discussion of exceptions and exception handling.)
How It Works
The way this class works is extremely simple. Instead of calling new to allocate blocks of raw memory, a MemPtr object of the size required is declared. The address-of operator is used to get the pointer address, and then the pointer is used normally. The only difference is that the MemPtr object will delete the memory when it falls out of scope.
For example, the code
MemPtr hmem( strlen(test_string) + 1 );
char* chararray = (char*) &hmem ;
is all that is necessarythe memory will be deleted when hmem falls out of scope.
The strength of this technique is especially evident when portable code has to be written because using the new and delete included with the compiler might not be the most efficient way to do things. Instead of calling the new and delete operators, the class could call operating system functions, or implement its own suballocation scheme.
13.2 Make a class that automatically cleans up objects allocated with new?
Problem
This is a different spin on the previous How-To. The previous How-To only allocated raw memory, leaving the host program to typecast the resulting pointer. The previous How-To is extremely useful if your program must use memory functions other than new or delete, for example, in 16-bit Windows programming. This How-To presents a better approach to dealing with objects allocated with new.
Technique
The Standard Template Library includes a class that is used as a self-managing pointer. Its the class I recommend using, but the purpose of this How-To is to implement our own to explain how it all works.
In the previous How-To, the memory management class handled the allocation and deletion of memory. In this case, pointers will be submitted to objects to manage. This poses a problem: How does the class know if it being given a pointer to an array of objects, or a pointer to a single object? The truth is that there is no standard way to figure this out, so for the purpose of this How-To, the classs constructors will be made to take an additional, optional argument to indicate that an array of objects is being managed.
Another problem is one of type safety. Dealing with void* data types is a potentially hazardous endeavor because they must be typecast to prevent compiler warnings, and can accidentally be assigned to the wrong data type. The solution here is to make this a template class.
|