GCO4020/CSC428 - Advanced Object Oriented Techniques In C++
Week 1
Array
could be extended to three or more
"dimensions". For example, if a "3D" strategy were used, Array
elements would still be stored in fixed-size
buckets, but these buckets would in turn be stored
in fixed-size "meta-buckets".
reallocate() and access would need to be
re-implemented. The private member myElement
would change from ElementType** to
ElementType*** and an extra counter
(myMetaBucketCount) would be needed to track
the allocation of meta-buckets.
Array objects
would be further reduced, since only the vector¤ of
pointers to meta-buckets would ever need to be
reallocated, and then only when every bucket in
every meta-bucket were full.
Given that n, d and D are non-negative integers and that D is
equal to 2 raised to the power of d:
d places divides
a number by 2 to the d, which was previously defined
to be D, as required.
1<<d produces a bit pattern with
the d'th bit set to 1 and all other bits zero.
Subtracting 1 from such a bit-pattern produces a bit
pattern with bits d-1, d-2,..., 1, 0
set to 1 and
all other bits zero (just as subtracting 1 from
1000000 gives 0999999 in base 10). A bitwise AND
(&) of this pattern with a number n therefore sets
all the bits greater than bit d-1 to zero, and
retains the remaining bits as they were.
Hence the expression n&((1<<d)-1) isolates the lowest
d bits of n.
Note that it is the very same lowest d bits which are lost
when n is right-shifted d places (that is, when n
is divided by D, as demonstrated in (i) above).
d places is equivalent to multiplying by
D. Now the modulo operation n%D can defined
as n-(n/D)*D, where / denotes integer
division. Hence to compute (n/D)*D we could
first right-shift n by d places and then
left-shift the result by d places. Since
left-shifting d places fills the lowest d
bits with zeros, the difference between n and
(n>>d)<<d will be the value of those lowest
d bits, or n&((1<<d)-1 as proved above.
n and (n>>d)<<d is equal to
n-n/D*D or n%D. Hence n&((1<<d)-1 is
equal to n%D, as required.
(End of solution)
Array class provided a single "pre-allocation constructor":
template <class ElementType>
Array<ElementType>::Array(int initsize,
const ElementType& initval,
bool empty = false)
: myBucketCount(0)
, myNextFree(empty?0:initsize)
, myElement(0)
{
reallocate((initsize+modBucketSize >> divBucketSize);
if (!empty)
{
for (int i=0;i<myNextFree;i++)
{
access(i) = initval;
}
}
}
then there would be no conflict with the special case of:
template <>
Array<int>::Array(const int& elem);
and "uninitialized" arrays could still be constructed thus:
Array<string> name (20, string(), true);
Array<double> score (20, double(), true);
Note however that this solution is considerably less convenient and
also less self-documenting.
(End of solution)
Array destructor must use
operator delete[] rather than operator delete
because all the memory allocated by reallocate() is
allocated using operator new[] ("new array of"), not
operator new ("new single instance of"). Using mismatched
versions of new and delete results in undefined
behaviour that typically corrupts the free store and is
always difficult to debug. Worse still, older compilers may
accept the mismatch, creating subtle, hard-to-find bugs when
compilers are upgraded or code ported to another system.
virtual the
class designer is (implicitly) asserting that class
Array should never be used as a public base class,
or at least that instances of its derived classes
should never be polymorphically delete'd through an
Array*. (TDCS, § ????).
(End of solution)
Array::Contains(...) along
the lines of Array::operator[], so that it
throws an exception on failure. It should be renamed
something like Array::IndexOf(...) since it can
no longer be used as a simple Boolean containment
test (for an approach which would provide a
better solution to this interface¤ design
problem and which does retain the desirable Boolean
test capability, see "Topic 17: Fallible Types".)
template <class ElementType>
struct Array<ElementType>::NoSuchElement
{
NoSuchElement(const ElementType& t) : target(t) {}
const ElementType& target;
};
template <class ElementType>
int
Array<ElementType>::IndexOf(const ElementType& target) const
{
for (int i=0;i<myNextFree;i++)
{
if (target == access(i)) { return i; }
}
throw NoSuchElement(target);
}
// AND LATER....
while (cin >> searchValue)
{
try
{
int index = array.IndexOf(searchValue);
cout << records[index] << endl;
}
catch (const Array<Record>::NoSuchElement& nse)
{
cout << "No match for " << nse.target << endl;
}
}
Array::Contains to search on any
(in)equality criterion we could give it a second parameter
which specified how to compare elements. The simplest, but
least flexible and least maintainable option would be to
use an enum value to control a switch specifying
the possible alternatives (see source code listing: EnumContains.C).
More flexibility could be gained by passing a function
pointer, which implements the operation (see source code listing: FnPtrContains.C). The most flexible solution
makes Array::Contains a
templated member function and uses so-called
"Comparators" (see "Topic 21: Functors I" and
(TDCS, § 20.3.3) for more
details.)
(End of solution)
Array::Append(const Array& array) is flawed because the for loop never
terminates if an array is appended to itself (for example:
array.Append(array);). This is because in that case the aliasing of
*this and array makes the proposed loop equivalent to:
for (int i=0; i<array.myNextFree; i++)
{
array.access(array.myNextFree++) = array.access(i);
}
which will never terminate (except in the trivial case where
array.myNextFree is initially 0), because array.myNextFree
is incremented as frequently as i.
(End of solution)
Array::Reset() and
Array::RemoveLast(...) delete any memory that becomes
unused:
template <class ElementType>
Array<ElementType>&
Array<ElementType>::Reset(void)
{
myNextFree = 0;
while (myBucketCount>0)
{
delete[] myElement[--myBucketCount];
}
delete[] myElement;
myElement = 0;
return *this;
}
template <class ElementType>
Array<ElementType>&
Array<ElementType>::RemoveLast(int n)
{
if (myNextFree<=n) { myNextFree = 0; }
else { myNextFree -= n; }
int newBucketCount = (myNextFree+modBucketSize>>divBucketSize);
while (myBucketCount>newBucketCount)
{
delete [] myElement[--myBucketCount];
}
if (newBucketCount==0)
{
delete [] myElement;
myElement = 0;
}
return *this;
}
Array object
is likely to store greatly exceeds the average number
of elements it will store.
The principal disadvantage
is that deletions are often slow, making the
removal operations expensive, particularly if an
alternating series of deletions and insertions are
performed, causing the memory management to
"thrash".
Array::Reset() is clearly a special case of the
more general Array::RemoveLast(...), and could be
coded as such, thereby localizing the implementation¤
of the removal components of the class to a single
member function. The downside of this approach are
a slight increase in the cost of calling
Array::Reset() because, being a special case,
it can be directly implemented slightly more efficiently.
template <class ElementType>
inline Array<ElementType>&
Array<ElementType>::Reset(void)
{
return RemoveLast(myNextFree);
}
(End of solution)
== is that we
avoid adding another constraint on
ElementType, namely that operator!= be
defined for ElementType (ElementType is
already required to define operator==, because
that operator is also required by
Array::Contains(...)).
Array equality is
reasonably obvious, it is less clear how to define
the "less-than" operation between two arrays. One
reasonable approach would be to generalize the
existing comparison rules for char[], as
implemented by the library function strcmp.
a1 is "less than" an array
a2 if there exists a index i such that
0 <= i < a2.Count() and either a1.Count() <= i
or a1[i] < a2[i].
operator< looks like
this:
template <class ElementType> bool operator< (const Array<ElementType>& a1, const Array<ElementType>& a2) { if (&a1==&a2) { return false; } for (int i=0; i<a2.myNextFree; i++) { if (a1.myNextFree <= i || a1.access(i) < a2.access(i)) { return true; } } return false; }
Last updated: Fri Feb 18 11:17:01 2000