|
|
Vector: A Dynamic Array ClassIntroductionThe standard Java class library contains a useful class called Vector. It is actually a kind of dynamic array. This assignment involves writing a C++ class, called DynArray, that emulates the Java Vector class. In addition, it involves writing other, related, classes, and is a useful exercise in the use of class inheritance and virtual functions. ConceptsTo implement such a dynamic class, it is necessary to use arrays of pointers, and to use new and delete to allocate and de-allocate memory for these arrays of pointers. Perhaps a picture would help: Here, the first five elements of the array (on the left) point to elements, while the remainder are empty (i.e., are NULL pointers). The topmost element is at index 0, the next at index 1, etc. When an element is 'added' to the array, a pointer to that element is entered into the next available pointer slot. If there is no space left in the pointer array, then the array is automatically expanded by capacityIncrement elements to make space. This expansion is accomplished by:
When an element is 'removed' from the array, the corresponding array pointer entry is zeroed, and then all the array pointer entries above it are moved down one, to fill in the gap (which means that their index values also change). All internal data in the class is updated to reflect the new state of the array. StrategyOur DynArray is an array of ... what? On the one hand, we'd like to implement a dynamic array that would be generally useful -- an array of Widgets, an array of Persons, an array of Vehicles, -- whatever. On the other hand, how do we do this without writing a new kind of array for each type of element we might want to store into it? We can solve this problem by using inheritance; we will implement a DynArray class that can store instances of any class derived from an abstract base class called DynArrayElement. So far, so good. But think about the problem some more. Wouldn't it be a good idea for an instance of each class derived from DynArrayElement to be informed when it is being added to a DynArray? and when it is being removed from a DynArray? For one thing, it would be nice if an element could determine whether it currently is in a DynArray, before it silently disappears as a result of being destroyed. For example:
Clearly, we would like to catch this error as early as possible. We can add code to the element's destructor to make us aware of this situation, if the element is in a DynArray. How can the element tell that it's stored in a DynArray? Actually, the element may be in several DynArrays simultaneously, or even in the same DynArray more than once! So, it makes sense to keep a count of how many arrays each element is currently in; if the count is not zero at the time its destructor is called, ERROR! How might we accomplish this? Well, let's require that every class derived from DynArrayElement must implement a standard set of virtual functions. We can do that by writing our DynArrayElement class to include them as pure virtual functions:
Function onInsertion() is called by the DynArray code when it inserts an element into itself, and function onRemoval() is called by the DynArray code when it removes it from itself. In each case, the function provides a reference to the DynArray instance to which the element is being added, in case the element needs to make use of that information. What each function does is totally dependent on the derived class. Indeed, different derived classes may do totally different things. These functions may keep a count of the number of arrays the instance is in, or they may perform more complex housekeeping. What they do, or should do, is the business of that class, and not of the DynArray class. So we delegate those functions to the element class, where it belongs.
Use of Exceptions
Several of the member functions of the DynArray class have to deal with exceptional conditions. For example, an attempt to access an element via an index, when there is no element at that index, should be invalid. In this case, the function will throw an exception -- specifically an ArrayOutOfBoundsException. To make our exceptions simpler to deal with, we organize them into a class hierarchy:
We do this so that we can catch a specific one of these exceptions by doing something like:
Alternatively, we can catch any of these exceptions by doing something like:
Since we are using these exceptions only for the DynArray class, we will place their declarations in the dynarray.h file. Requirements for the
|
#ifndef _DOUBLE_H
#define _DOUBLE_H
//
// double.h
//
#include "dynarray.h"
class Double : public DynArrayElement
{
public:
Double(double d);
Double(const Double &d);
Double &operator=(const Double &d);
virtual ~Double();
double DoubleValue() const; // return m_value
virtual void OnInsertion(DynArray &array);
virtual void OnRemoval(DynArray &array);
private:
double m_value; // The encapsulated value
int m_inArrayCount; // Count of array memberships
// (initialized to 0)
};
#endif // _DOUBLE_H
|
Here's what you'll need to implement the Double class
Note that it is a good idea to 'instrument' your code with stuff like:
cout << "Double element inserted into DynArray" << endl;
so you can trace what's happening during execution.
Write yourself a test program to test that everything works.
Be Thorough!
Change your DynArray class so that it is derived from DynArrayElement. Why might this be a good idea?
Does your class now compile? What do you have to do to make it compile?
Here is a function you can use to test this out:
void TestIt()
{
DynArray parent;
DynArray child1, child2, child3;
parent.AddElement(child1);
parent.AddElement(child2);
parent.AddElement(child3);
Double d1(1.0), d2(2.0), d3(3.0);
child1.AddElement(d1);
child1.AddElement(d2);
child1.AddElement(d3);
Double d4(4.0), d5(5.0), d6(6.0);
child2.AddElement(d4);
child2.AddElement(d5);
child2.AddElement(d6);
Double d7(7.0), d8(8.0), d9(9.0);
child3.AddElement(d7);
child3.AddElement(d8);
child3.AddElement(d9);
cout << " ";
for (int k = 0; k < child1.ElementCount(); k++)
cout << "[" << k << "] ";
cout << endl;
for (int i = 0; i < parent.ElementCount(); i++)
{
DynArray &child = (DynArray &)(parent[i]);
cout << "[" << i << "]: ";
for (int j = 0; j < child.ElementCount(); j++)
{
Double &d = (Double &)(child[j]);
cout << d.DoubleValue() << " ";
}
cout << endl;
}
child3.RemoveAllElements();
child2.RemoveAllElements();
child1.RemoveAllElements();
parent.RemoveAllElements();
}
|
Before you get too concerned about the amount of work you might have to do for this assignment, here's some statistics from my implementation, including the enhancements:
| This page was last changed on 06 May 2006 |