Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++
Prev Contents / Index Next

Overloading new & delete for a class

Although you don’t have to explicitly say static, when you overload new and delete for a class, you’re creating static member functions. As before, the syntax is the same as overloading any other operator. When the compiler sees you use new to create an object of your class, it chooses the member operator new( ) over the global version. However, the global versions of new and delete are used for all other types of objects (unless they have their own new and delete).

In the following example, a primitive storage allocation system is created for the class Framis. A chunk of memory is set aside in the static data area at program start-up, and that memory is used to allocate space for objects of type Framis. To determine which blocks have been allocated, a simple array of bytes is used, one byte for each block:

//: C13:Framis.cpp
// Local overloaded new & delete
#include <cstddef> // Size_t
#include <fstream>
#include <iostream>
#include <new>
using namespace std;
ofstream out("Framis.out");

class Framis {
  enum { sz = 10 };
  char c[sz]; // To take up space, not used
  static unsigned char pool[];
  static bool alloc_map[];
public:
  enum { psize = 100 };  // frami allowed
  Framis() { out << "Framis()\n"; }
  ~Framis() { out << "~Framis() ... "; }
  void* operator new(size_t) throw(bad_alloc);
  void operator delete(void*);
};
unsigned char Framis::pool[psize * sizeof(Framis)];
bool Framis::alloc_map[psize] = {false};

// Size is ignored -- assume a Framis object
void* 
Framis::operator new(size_t) throw(bad_alloc) {
  for(int i = 0; i < psize; i++)
    if(!alloc_map[i]) {
      out << "using block " << i << " ... ";
      alloc_map[i] = true; // Mark it used
      return pool + (i * sizeof(Framis));
    }
  out << "out of memory" << endl;
  throw bad_alloc();
}

void Framis::operator delete(void* m) {
  if(!m) return; // Check for null pointer
  // Assume it was created in the pool
  // Calculate which block number it is:
  unsigned long block = (unsigned long)m
    - (unsigned long)pool;
  block /= sizeof(Framis);
  out << "freeing block " << block << endl;
  // Mark it free:
  alloc_map[block] = false;
}

int main() {
  Framis* f[Framis::psize];
  try {
    for(int i = 0; i < Framis::psize; i++)
      f[i] = new Framis;
    new Framis; // Out of memory
  } catch(bad_alloc) {
    cerr << "Out of memory!" << endl;
  }
  delete f[10];
  f[10] = 0;
  // Use released memory:
  Framis* x = new Framis;
  delete x;
  for(int j = 0; j < Framis::psize; j++)
    delete f[j]; // Delete f[10] OK
} ///:~

The pool of memory for the Framis heap is created by allocating an array of bytes large enough to hold psize Framis objects. The allocation map is psize elements long, so there’s one bool for every block. All the values in the allocation map are initialized to false using the aggregate initialization trick of setting the first element so the compiler automatically initializes all the rest to their normal default value (which is false, in the case of bool).

The local operator new( ) has the same syntax as the global one. All it does is search through the allocation map looking for a false value, then sets that location to true to indicate it’s been allocated and returns the address of the corresponding memory block. If it can’t find any memory, it issues a message to the trace file and throws a bad_alloc exception.

This is the first example of exceptions that you’ve seen in this book. Since detailed discussion of exceptions is delayed until Volume 2, this is a very simple use of them. In operator new( ) there are two artifacts of exception handling. First, the function argument list is followed by throw(bad_alloc), which tells the compiler and the reader that this function may throw an exception of type bad_alloc. Second, if there’s no more memory the function actually does throw the exception in the statement throw bad_alloc. When an exception is thrown, the function stops executing and control is passed to an exception handler, which is expressed as a catch clause.

In main( ), you see the other part of the picture, which is the try-catch clause. The try block is surrounded by braces and contains all the code that may throw exceptions – in this case, any call to new that involves Framis objects. Immediately following the try block is one or more catch clauses, each one specifying the type of exception that they catch. In this case, catch(bad_alloc) says that that bad_alloc exceptions will be caught here. This particular catch clause is only executed when a bad_alloc exception is thrown, and execution continues after the end of the last catch clause in the group (there’s only one here, but there could be more).

In this example, it’s OK to use iostreams because the global operator new( ) and delete( ) are untouched.

The operator delete( ) assumes the Framis address was created in the pool. This is a fair assumption, because the local operator new( ) will be called whenever you create a single Framis object on the heap – but not an array of them: global new is used for arrays. So the user might accidentally have called operator delete( ) without using the empty bracket syntax to indicate array destruction. This would cause a problem. Also, the user might be deleting a pointer to an object created on the stack. If you think these things could occur, you might want to add a line to make sure the address is within the pool and on a correct boundary (you may also begin to see the potential of overloaded new and delete for finding memory leaks).

operator delete( ) calculates the block in the pool that this pointer represents, and then sets the allocation map’s flag for that block to false to indicate the block has been released.

In main( ), enough Framis objects are dynamically allocated to run out of memory; this checks the out-of-memory behavior. Then one of the objects is freed, and another one is created to show that the released memory is reused.

Because this allocation scheme is specific to Framis objects, it’s probably much faster than the general-purpose memory allocation scheme used for the default new and delete. However, you should note that it doesn’t automatically work if inheritance is used (inheritance is covered in Chapter 14).

Thinking in C++
Prev Contents / Index Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire