|
|
|
|
Stash for pointers
This new version of the Stash
class, called PStash, holds pointers to objects that exist by
themselves on the heap, whereas the old Stash in earlier chapters copied
the objects by value into the Stash container. Using new and
delete, it’s easy and safe to hold pointers to objects that have
been created on the heap.
Here’s the header file for the
“pointer Stash”:
//: C13:PStash.h
// Holds pointers instead of objects
#ifndef PSTASH_H
#define PSTASH_H
class PStash {
int quantity; // Number of storage spaces
int next; // Next empty space
// Pointer storage:
void** storage;
void inflate(int increase);
public:
PStash() : quantity(0), storage(0), next(0) {}
~PStash();
int add(void* element);
void* operator[](int index) const; // Fetch
// Remove the reference from this PStash:
void* remove(int index);
// Number of elements in Stash:
int count() const { return next; }
};
#endif // PSTASH_H ///:~
The underlying data elements are fairly
similar, but now storage is an array of void pointers, and the
allocation of storage for that array is performed with
new instead of malloc( ). In the
expression
void** st = new void*[quantity + increase];
the type of object allocated is a
void*, so the expression allocates an array of void
pointers.
The destructor deletes the storage where
the void pointers are held rather than attempting to delete what they
point at (which, as previously noted, will release their storage and not call
the destructors because a void
pointer has no type
information).
The other change is the replacement of
the fetch( ) function with operator[
], which makes more sense syntactically. Again,
however, a void* is returned, so the user must remember what types are
stored in the container and cast the pointers when fetching them out (a problem
that will be repaired in future chapters).
Here are the member function
definitions:
//: C13:PStash.cpp {O}
// Pointer Stash definitions
#include "PStash.h"
#include "../require.h"
#include <iostream>
#include <cstring> // 'mem' functions
using namespace std;
int PStash::add(void* element) {
const int inflateSize = 10;
if(next >= quantity)
inflate(inflateSize);
storage[next++] = element;
return(next - 1); // Index number
}
// No ownership:
PStash::~PStash() {
for(int i = 0; i < next; i++)
require(storage[i] == 0,
"PStash not cleaned up");
delete []storage;
}
// Operator overloading replacement for fetch
void* PStash::operator[](int index) const {
require(index >= 0,
"PStash::operator[] index negative");
if(index >= next)
return 0; // To indicate the end
// Produce pointer to desired element:
return storage[index];
}
void* PStash::remove(int index) {
void* v = operator[](index);
// "Remove" the pointer:
if(v != 0) storage[index] = 0;
return v;
}
void PStash::inflate(int increase) {
const int psz = sizeof(void*);
void** st = new void*[quantity + increase];
memset(st, 0, (quantity + increase) * psz);
memcpy(st, storage, quantity * psz);
quantity += increase;
delete []storage; // Old storage
storage = st; // Point to new memory
} ///:~
The add( ) function is
effectively the same as before, except that a pointer is stored instead of a
copy of the whole object.
The inflate( ) code is
modified to handle the allocation of an array of void* instead of the
previous design, which was only working with raw bytes. Here, instead of using
the prior approach of copying by array indexing, the Standard C library function
memset( ) is first used to set all the new
memory to zero (this is not strictly necessary, since the PStash is
presumably managing all the memory correctly – but it usually
doesn’t hurt to throw in a bit of extra care). Then
memcpy( ) moves the existing data from the
old location to the new. Often, functions like memset( ) and
memcpy( ) have been optimized over time, so they may be faster than
the loops shown previously. But with a function like inflate( ) that
will probably not be used that often you may not see a performance difference.
However, the fact that the function calls are more concise than the loops may
help prevent coding errors.
To put the responsibility of object
cleanup squarely on the shoulders of the client programmer, there are two ways
to access the pointers in the PStash: the operator[], which simply
returns the pointer but leaves it as a member of the container, and a second
member function remove( ), which returns the pointer but also
removes it from the container by assigning that position to zero. When the
destructor for PStash is called, it checks to make sure that all object
pointers have been removed; if not, you’re notified so you can prevent a
memory leak (more elegant solutions will be forthcoming in later
chapters).
|
|
|