Home arrow C++ arrow Page 10 - A Simple Garbage Collector for C++
C++

A Simple Garbage Collector for C++


The use of dynamically allocated memory must be managed, because it has a tremendous effect on the performance of your programs. The current trend in handling dynamic memory seems to be shifting toward an automated approach. While C++ uses the manual approach for managing dynamic memory, this does not mean that it can't be automated in that language -- thus giving the C++ programmer the best of both worlds. This article explains how to do it. It is excerpted from chapter two of The Art of C++, written by Herbert Schildt (McGraw-Hill/Osborne, 2004; ISBN: 0072255129).

Author Info:
By: McGraw-Hill/Osborne
Rating: 4 stars4 stars4 stars4 stars4 stars / 73
June 21, 2005
TABLE OF CONTENTS:
  1. · A Simple Garbage Collector for C++
  2. · Comparing the Two Approaches to Memory Management
  3. · Choosing a Garbage Collection Algorithm
  4. · What About auto_ptr?
  5. · An Overview of the Garbage Collector Classes
  6. · GCPtr In Detail
  7. · The Overloaded Assignment Operators
  8. · GCInfo
  9. · How to Use GCPtr
  10. · Allocating Arrays
  11. · A Larger Demonstration Program
  12. · Load Testing

print this article
SEARCH DEVARTICLES

A Simple Garbage Collector for C++ - Allocating Arrays
(Page 10 of 12 )

If you are allocating an array using new, then you must tell GCPtr this fact by specifying its size when the GCPtr pointer to that array is declared. For example, here is the way to allocate an array of five doubles:

GCPtr<double, 5> pda = new double[5];

The size must be specified for two reasons. First, it tells the GCPtr constructor that this object will point to an allocated array, which causes the isArray field to be set to true. When isArray is true, the collect( ) function frees memory by using delete[ ], which releases a dynamically allocated array, rather than delete, which releases only a single object. Therefore, in this example, when pda goes out of scope, delete[ ] is used and all five elements of pda are freed. Ensuring that the correct number of objects are freed is especially important when arrays of class objects are allocated. Only by using delete[ ] can you know that the destructor for each object will be called.

The second reason that the size must be specified is to prevent an out-of-bounds element from being accessed when an Iter is used to cycle through an allocated array. Recall that the size of the array (stored in arraySize) is passed by GCPtr to Iters constructor whenever an Iter is needed.

Be aware that nothing enforces the rule that an allocated array be operated on only through a GCPtr that has been specified as pointing to an array. This is solely your responsibility.

Once you have allocated an array, there are two ways you can access its elements. First, you can index the GCPtr that points to it. Second, you can use an iterator. Both methods are shown here.

Using Array Indexing

The following program creates a GCPtr to a 10-element array of ints. It then allocates that array and initializes it to the values 0 through 9. Finally, it displays those values. It performs these actions by indexing the GCPtr.

// Demonstrate indexing a GCPtr.
#include <iostream>
#include <new>
#include "gc.h"
using namespace std;
int main() {
 
try {
    // Create a GCPtr to an allocated array of 10 ints.
   
GCPtr<int, 10> ap = new int[10];
   
// Give the array some values using array indexing.
    for(int i=0; i < 10; i++)
      ap[i] = i;
   
// Now, show the contents of the array.
    for(int i=0; i < 10; i++)
      cout << ap[i] << " ";
   
cout << endl;
 
} catch(bad_alloc exc) {
    cout << "Allocation failure!\n";
    return 1;
 
}
 
return 0;
}
The output, with the display option off, is shown here:

0 1 2 3 4 5 6 7 8 9

Because a GCPtr emulates a normal C++ pointer, no array bounds checking is performed, and it is possible to overrun or under run the dynamically allocated array. So, use the same care when accessing an array through a GCPtr as you do when accessing an array through a normal C++ pointer.

Using Iterators

Although array indexing is certainly a convenient method of cycling through an allocated array, it is not the only method at your disposal. For many applications, the use of an iterator will be a better choice because it has the advantage of preventing boundary errors. Recall that for GCPtr, iterators are objects of type Iter. Iter supports the full complement of pointer operations, such as ++. It also allows an iterator to be indexed like an array.

Here is the previous program reworked to use an iterator. Recall that the easiest way to obtain an iterator to a GCPtr is to use GCiterator, which is a typedef inside GCPtr that is automatically bound to the generic type T.

// Demonstrate an iterator.
#include <iostream>
#include <new>
#include "gc.h"
using namespace std;
int main() {
 
try {
    // Create a GCPtr to an allocated array of 10 ints.  
    GCPtr<int, 10> ap = new int[10];
   
// Declare an int iterator.
   
GCPtr<int>::GCiterator itr;
   
// Assign itr a pointer to the start of the array.
   
itr = ap.begin();
   
// Give the array some values using array indexing.
    for(unsigned i=0; i < itr.size(); i++)
      itr[i] = i;
   
// Now, cycle through array using the iterator.
    for(itr = ap.begin(); itr != ap.end(); itr++)
      cout << *itr << " ";
   
cout << endl;
 
} catch(bad_alloc exc) {
   
cout << "Allocation failure!\n";
   
return 1;
  } catch(OutOfRangeExc exc) {
    cout << "Out of range access!\n";
    return 1;
 
}
  return 0;
}

On your own, you might want to try incrementing itr so that it points beyond the boundary of the allocated array. Then try accessing the value at that location. As you will see, an OutOfRangeExc is thrown. In general, you can increment or decrement an iterator any way you like without causing an exception. However, if it is not pointing within the underlying array, attempting to obtain or set the value at that location will cause a boundary error.

Using GCPtr with Class Types

GCPtr is used with class types in just the same way it is used with built-in types. For example, here is a short program that allocates objects of MyClass:

// Use GCPtr with a class type.
#include <iostream>
#include <new>
#include "gc.h"
using namespace std;
class MyClass {
  int a, b;
public:
  double val;
 
MyClass() { a = b = 0; }
 
MyClass(int x, int y) {
    a = x;
    b = y;
    val = 0.0;
 
}
 
~MyClass() {
    cout << "Destructing MyClass(" <<
         a << ", " << b << ")\n";
  }
 
int sum() {
    return a + b;
  }
 
friend ostream &operator<<(ostream &strm, MyClass &obj);
};
// An overloaded inserter to display MyClass.
ostream &operator<<(ostream &strm, MyClass &obj) {
  strm << "(" << obj.a << " " << obj.b << ")";
  return strm;
}
int main() {
  try {
    GCPtr<MyClass> ob = new MyClass(10, 20);
   
// Show value via overloaded inserter.
    cout << *ob << endl;
   
// Change object pointed to by ob.
    ob = new MyClass(11, 21);
   
cout << *ob << endl;
   
// Call a member function through a GCPtr.
    cout << "Sum is : " << ob->sum() << endl;
   
// Assign a value to a class member through a GCPtr.  
    ob->val = 98.6;
    cout << "ob->val: " << ob->val << endl;
   
cout << "ob is now " << *ob << endl;
  } catch(bad_alloc exc) {
   
cout << "Allocation error!\n";
   
return 1;
  }
 
return 0;
}

Notice how the members of MyClass are accessed through the use of the > operator. Remember, GCPtr defines a pointer type. Thus, operations through a GCPtr are performed in exactly the same fashion that they are with any other pointer.

The output from the program, with the display option turned off, is shown here:

(10 20)
(11 21)
Sum is : 32
ob->val: 98.6
ob is now (11 21)
Destructing MyClass(11, 21)
Destructing MyClass(10, 20)

Pay special attention to the last two lines. These are output by ~MyClass( ) when garbage is collected. Even though only one GCPtr pointer was created, two MyClass objects were allocated. Both of these objects are represented by entries in the garbage collection list. When ob is destroyed, gclist is scanned for entries having a reference count of zero. In this case, two such entries are found, and the memory to which they point is deleted.


blog comments powered by Disqus
C++ ARTICLES

- Intel Threading Building Blocks
- Threading Building Blocks with C++
- Video Memory Programming in Text Mode
- More Tricks to Gain Speed in Programming Con...
- Easy and Efficient Programming for Contests
- Preparing For Programming Contests
- Programming Contests: Why Bother?
- Polymorphism in C++
- Overview of Virtual Functions
- Inheritance in C++
- Extending the Basic Streams in C++
- Using Stringstreams in C++
- Custom Stream Manipulation in C++
- General Stream Manipulation in C++
- Serialize Your Class into Streams in C++

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials