GCO4020/CSC428 - Advanced Object Oriented Techniques In C++
Week 5
void
argument) constructor,
string class fails all four of these requirements and so cannot
be a member of a union. Hence a separate member is needed to store string
values.
(End of solution)
Value include:
operator! (as a complement to operator bool),
operator+ friend function (most probably
implemented using Value::operator+=),
operator- and binary operator-=,
operator* and operator*=,
operator/ and operator/=,
operator% and operator%=,
operator string),
operator[] (to index into string values),
length() member (to return the length of
string values).
preincrement, addassign and assign, the convertToBool function
cannot change the value or the type of value that its letter stores (under
any reasonable semantics for the convert-to-bool operation). Hence
there is no need to pass a reference to the surrounding ValueEnv's
myLetter, as it will never need to be changed.
(End of solution)
letter_ref refers to the myLetter pointer in the
ValueEnv envelope containing the current ValueLet letter.
That member is the only pointer to the current letter. If
this pointer were not deleted before being reassigned, after the
assignment there would be no pointer to the old letter, and
the memory allocated for that object would "leak¤".
ValueLet::assign() is called (from ValueEnv::operator=) like this:
myLetter->assign(myLetter,v.myLetter);
Hence, within assign(), the parameter letter_ref points to the
current object (that is *this). Therefore, deleting letter_ref
deletes the current object (!)
The assign() member function will continue to execute
successfully even though its this object to which the member function
belongs has ceased to exist.
However, because this no longer points to a valid object, after
the delete it is not possible to refer to or access any members of
the original object. Hence ValueLet::assign() must be carefully coded
to obey this restriction.
(End of solution)
To be able to store complex<double>s inside a Value object
the following steps would be required:
Value::myVal_Complex)
to store the new type of value (which can't be stored
in the myVal union for the same reason that a
string couldn't).
vComplex) to the
ValueType enum.
Value constructor which takes a
complex<double> and initializes myVal_Complex
and myType appropriately.
vCount constant.
switch statement. Add multiple cases to the
switch inside Value::operator+=(),
corresponding to the various combinations of valid
left and right operands.
complex<double>s inside a ValueEnv object the
following steps would be required:
ValueComplex) from
ValueLet. The new class will have one data member
- a complex<double> called myVal.
ValueEnv constructor which takes a
complex<double> and initializes myLetter by
allocating a new ValueComplex.
complex<double>. For
example, ValueDouble::addassign() would need an extra case
if it was to cope with the addition of a complex:
void ValueDouble::addassign (ValueLet*& letter_ref, const ValueLet* val) { if (typeid(*val) == typeid(ValueInt)) { myVal += static_cast<const ValueInt*>(val)->Val(); } else if (typeid(*val) == typeid(ValueDouble)) { myVal+=static_cast<const ValueDouble*>(val)->myVal; } else if (typeid(*val) == typeid(ValueComplex)) { complex<double> sum = myVal + static_cast<const ValueComplex*>(val)->Val(); delete letter_ref; letter_ref = new ValueComplex(sum); } else { delete letter_ref; letter_ref = new ValueNull; } }
(End of solution)
v1 is allocated a ValueBool letter, whilst
v2 is allocated a ValueString letter,
ValueEnv::operator++ calls
ValueBool::preincrement which in turn changes
v1's letter to a ValueNull,
v2's ValueEnv::operator+= with
the resultant "null" v1 calls
v2.myLetter->addassign(), which detects the
assignment of a ValueNull and does nothing,
v3 is allocated a ValueInt letter, whilst
v4 is allocated a ValueDouble letter,
v4's ValueEnv::operator+= calls
ValueInt::addassign(), which detects the
assignment of a ValueDouble and delete
v4.myLetter, replacing it with a newly
allocated ValueDouble.
ValueType<void> is not valid. This is
because it is equivalent to the following code, which has (at least)
three fatal errors, as indicated:
template <> class ValueType<void> : public ValueLet { public: ValueType(void val); // INVALID PARAMETER LIST
virtual ValueLet* copy(void) const;
virtual bool convertToBool (void) const;
virtual void preincrement (ValueLet*&);
virtual void addassign (ValueLet*&, const ValueLet*);
const void& Val(void) const; // NO SUCH THING AS void&
private: void myVal; // INVALID DECLARATION };
These problems could be overcome by explicitly specializing the
entire ValueType<void> class, but it is easier and
more consistent just to provide a "void-like object" class (that
is, class Null) to overcome these problems.
(End of solution)
ValueType could be
established by templating a suitable friend declaration:
template <class Type> class ValueType : public ValueLet { template <class OtherType> friend class ValueType<OtherType>;
public: // THE REST OF THE TEMPLATE, AS BEFORE };
(End of solution)
ValueLet base class
(for example the definition of the assign()
member function), and "vertically" into the
ValueType template class (for example the
definition of the copy() member function).
complex<double>s inside a ValueEnv object the
following steps would now be required:
ValueEnv constructor which takes a
complex<double> and initializes myLetter by allocating
a new ValueType< complex<double> >.
ValueType< complex<double> >
to implement any semantics not adequately handled by the
templated defaults.
complex<double>. For example, ValueType<double>::addassign()
would need an extra case if it was to cope with the
addition of a complex number.
(End of solution)
ValueEnv constructors
naturally suggests replacing them with a single member
template (except, of course, for ValueEnv::ValueEnv(void) and
ValueEnv::ValueEnv(const char*), which do not conform
to the general structural pattern).
ValueEnv class to
store values of any type, since
initializing a ValueEnv with an object of a given
type, T, would then automatically generate the
required constructor (ValueEnv::ValueEnv(const T&))
and thereby initalize the ValueEnv with a(n
automatically generated) letter of type ValueType<T>
able to store values of type T.
class ValueEnv { public: template <class T> ValueEnv(const T& val) : myLetter(new ValueType<T>(val)) {}
ValueEnv::ValueEnv(void); ValueEnv::ValueEnv(const char*); ValueEnv::ValueEnv(const ValueEnv&);
//ETC. AS BEFORE };
ValueEnv must
possess the necessary properties to correctly
instantiate the various member functions of the
ValueType template. That is, type T must specify
operators for conversion-to-bool, preincrement,
add-assignment, and assignment.
res becoming "null", not 1+i as might be
reasonably expected.
intval-2 is added to val,
a ValueType< complex<int> > is generated. Unfortunately,
the existing ValueType<int> letter inside val has no way of
knowing how to add a ValueType< complex<int> > to
itself, and so the add-assign operation will default to replacing the letter
inside val with a ValueType<Null>.
ValueEnv val; ValueEnv res (2);
complex<int> intval (1,1);
val += intval-2; res += val;
Last updated: Fri Feb 18 11:17:42 2000