Object defines the fundamental protocols for the following concepts: Class
Descriptors, Dynamic Type-Checking, Dynamic Creation, Object Copying,
Object Input/Output, Change Propagation, and Object Comparison.
Furthermore, there are protocols covering Error Handling, Flag Handling,
and a binding to the ET++ Programming Environment. Object also defines a
virtual destructor which is extremely important.
Furthermore, every regular object is registered in the global object table
within Object's constructor, and unregistered within Object's destructor.
Use of the object table is optional (see the src/makefile), but it is
required by the Inspector.
Class Descriptors and Dynamic Type-Checks
Every regular class has an associated class descriptor providing
information about the class at run-time. The class descriptor itself is an
regular object, namely an instance of the class Class. The most
important information in a class descriptor are the name of the class it
belongs to, and a pointer to the class descriptor of the base class. Additionally,
the class descriptor holds an so-called prototype object. Prototype objects
are particularly important for an portable implementation of the New operation
(see below).
The information contained in the class descriptor is what the term meta
info refers to. Since other operations of Object's protocols require the
presence of the class descriptor, it is absolutely essential. Examples are
DeepClone (copying), ReadFrom and PrintOn (Object I/O).
Every regular object can be asked for its class descriptor by IsA. IsA and
also IsKindOf allow for dynamic type-checks. Furthermore, there is the Guard
construct that provides type-safe casts. A class descriptor can be also
obtained either by the Meta construct or from the class manager, a single
global object.
In order to support the ET++ Programming Environment, the class descriptor also
contains references into the source code and informations about name and
static type of the instance variables, provided they have been listed
in the meta implementation.
The class descriptor for a class is set up by MetaDef and MetaImpl.
MetaDef is part of the class's declaration, and MetaImpl of the
class's implementation. The following example shows how it is typically done:
// file: A.h #include "Object.h" class A : public Object { Object *anObject; int aNumber; public: MetaDef(A); A(); ... }; // file: A.C MetaImpl(A, (TP(anObject), T(aNumber), 0));
Dynamic Creation and Object Copying
Beside its information keeping task, the class descriptor is responsible
for the dynamic creation of new working objects. Dynamic creation is needed
since the C++ new operator requires that the class name is supplied, thus,
it is not possible to create an object without statically specifying its class
name. The New operation allows to directly ask an existing object for a new
working object of the same class.
Object also generically implements two methods for copying objects. method Clone returns a shallow copy, method
DeepClone creates a deep copy of the receiver.
A shallow copy is an object that is initialized exactly the same way as the
copy constructor of its class would. Clone is part of Object's interface for
the same reason as New. The clone generally shares its part objects with the source
object (the receiver). Which referenced objects are parts, and which objects
are to be duplicated although they are parts of their containing object, depends
on the precise semantics of the class.
Conceptually, the method DeepClone returns a deep copy of the
receiver by recursively applying a DeepClone to all part objects of the
receiver. The generic implementation of DeepClone uses Object
I/O (with streams operating on a buffer in memory).
The FreeAll operation is somewhat related to Object Copying. It allows to
recursively delete the part objects. As a rule-of-thumb, FreeAll is
applied to all part objects (and auxilliary objects, off course), but not
to acquainted objects.
Object Input/Output
The Object I/O facility writes and reads arbitrarily complex
object graphs in a textual, machine-independent format to a stream (by
operator<< and @Object::PrintOn), or from a stream (by operator>>and
@Object::ReadFrom), respectively. ET++ Object I/O takes an half-automated
object I/O approach.
The stream may operate on a file of the operating system, on a buffer in
memory, or whatever might be hidden by a stream. Object I/O properly
handles circular references. In other words, an object graph is linearized
when output, and correctly rebuilt when input.
Identification of objects due to linearization during input and output is
done by operator<<, and operator>>, respectively. The identity of
objects bases on pointer identity, thus a pointer book-keeping is
maintained during object I/O operations. When during output an object is
encountered that has been already output, then an index identifying that
object is output, no more the object itself. If such an index has been read
during input, the pointer variable is assigned the pointer of the already
existing object. This is done by operator>>. If the object to be input
has not yet been constructed, operator>> allocates an object of the
appropriate class, and the object is initialized by ReadFrom. In the
current version, the pointer book-keeping is maintained by the class
manager. In future versions, this is accomplished by the streams
themselves.
The primary purpose of PrintOn and ReadFrom is to apply operator<<, or
operator>> respectively, to their instance variables. Both methods are
hooks for their corresponding shift operators. Furthermore, ReadFrom has
constructor-like qualities, because the instance variables are initialized
such that a working object results. Notice that the operator>> allocates
memory, so it has a similar role like the operator new.
The client has to properly override PrintOn and ReadFrom only to get
Object I/O working (assuming that the class descriptor is present).
If the client introduces new built-ins, he has to provide appropriate
overloadings of operator<< and operator>>. See the built-in class Point,
for instance.
Object I/O additionally supports Dynamic Linking of classes;
when an class is encountered during input whose code is currently not
present in the image of the application's process, ET++ tries to find the
object code for that class and links it to the running application (see
technote 'Dynamic Linking'; there is no further coverage in
Object's reference documentation on this topic).
Object Comparison
If objects of a certain class are to be put into collections, then the class
should override the comparison methods. Collection classes call
the method IsEqual for searching and the method Compare
for sorting. The class Set uses the method Hash for
fast access to the objects.
Examples for these methods can be found in the class ByteArray,
the class ObjFloat or the class VObject.
Change Propagation
The Change Propagation mechanism supports the distribution (or
"propagation") of conceptual events with a minimal coupling between the object
where the state transition happened, and the objects that shall react on this
event. Change Propagation is most often used to establish a binding
between models and views in the sense of MVC.
Objects interested in changes of another object register themselves as
observers of that object with the method AddObserver.
An observer is unregistered by method RemoveObserver. The
observers of an object can be viewed as an identity set. The observed
object announces changes in its state through the method Send, and the observers are notified by invocation of method DoObserve. The death of the observed object is also such a
conceptual event, and observers are notified in this special case,
too.
Change Messages are not explicitly modelled as objects. A change message
consists of two codes describing first the role of the changed object (so-called
id's), and second the changed aspect (so-called part codes). A value whose
type and semantics depend on the id and the part code may further describe the
change. The sender of the message is available to the observers so
they can inquire of the observed object.
Change Propagation includes the concept of Delayed Changes. For
every object, there is an so-called delayed changes message counter. For
each DelayChanges call, the counter is incremented by one. The change
messages are eventually forwarded to the observers, if the delayed changes
counter is zero or gets zero after the according number of calls to
FlushChanges. The counter is initially zero, and cannot drop below zero.
If an observed object is deleted and there are still pending change
messages, they are simply discarded. It is implementation-dependent what
happens with delayed change messages sent at a time at which the sender has
no observers.
Examples:
The class Text uses change propagation, so an observing class TextView can update the display when the text changes. The text
classes also use the delayed change mechanism.
The Inspector of the ET++ Programming Environment also uses change
propagation by registering itself as an observer of the currently inspected
object.
Flag Handling
Object manages a set of flags which are stored
efficiently in the instvar flags. Some of the flags are used
internally. New flags may be defined in subclasses.
Flags for a class are defined by an enumeration type and by
means of the macro BIT as in the following example:
enum CommandFlags { eCmdCanUndo = BIT(eObjLast+1), eCmdCausesChange = BIT(eObjLast+2), ... eCmdNoReplFeedback = BIT(eObjLast+8), eCmdLast = eObjLast + 8 };The enumeration values must always be defined relative to the last flag of a super class (in the above example Object). Thus, the value of the first flag is determined this way:
- find the nearest super class which added some flags. - take the last enumeration value of that class. - increment it by one.In the above example the first flag for subclasses of the class Command would be BIT(eCmdLast+1).
Error Handling
Object provides a general error handling facility which uniformly
handles program and system errors.
There are 4 error handling methods (method Warning, method Error, method SysError and method Fatal) which
represent 4 different error levels indicating the severeness of the error.
These methods take the following arguments: the location (the name of the
method where the error occurred) and a format string in the style of printf with optional arguments.
The default error handler of ET++ prints the error message on the standard
error output. See technote 'Error Handling'.
Connection to the ET++ Programming Environment (ET++PE)
The ET++PE depends on the metaclass information (see
"Metaclasses" above) to be able to display static and dynamic
relationships between classes and objects and to find the location of the
source files of a class. Besides that, the Inspector can display instance
variables only if they have been included in the macro
MetaImpl.
The ET++PE can additionally be supported by overriding the method
Parts and the method InspectorId. The method Parts
allows to add important referenced objects to a list which is used by the
Object Structure Browser to show dynamic relationships between objects. The
method InspectorId should return an identification string which
is displayed by the Inspector and other views of the ET++PE. For example,
the method Document::InspectorId returns the document name.
The Inspector and the Source Code Browser of the ET++PE can also be started
directly through method Inspect and method EditSource.
classes are always derived from Object.
class Object
is never reused directly.
class Object is abstract.
class Object contains 54 methods.
Foundation