|
||
Last updated on April the 30th 1997. | Back to OPaC bright ideas ! |
In order to increase the readability of the sources, the
following typedef
s are defined for each type Typ
by using one of the type definition macros (such as OP_TYPEDEF (Typ);
) :
typedef Typ* TypPtr; // pointer to type typedef const Typ& TypIn; // input argument - do not modify typedef Typ& TypOut; // output argument typedef Typ& TypInOut; // input and output argument
The In
, Out
and InOut
types are used for passing objects, structures and other complex
data types. Numbers also use these type definitions, but the In
suffix is often being dropped, since it is not useful to transmit
small amounts of data through reference rather than through copy.
The following function definitions are therefore equivalent :
Bool FindCell (OPListIn list, Count index, OPCoordOut width, OPCellOut cell); Bool FindCell (const OPListRef& list, Count index, OPCoord& width, OPCellRef& cell);
Bool
is the fundamental boolean data type
(either FALSE
or TRUE
). OPList
and OPCell
are both object classes (this is a
special case, see below), OPCoord
is a co-ordinate
specification (usually a real number) and Count
is a
simple unsigned 32-bit integer.
Objects used as arguments to functions (such as OPList
)
or methods are in fact transmitted through the use of smart
pointers; for class Typ
, the following
equivalencies are used :
typedef const TypRef& TypIn; // input argument - do not modify typedef TypRef& TypOut; // output argument typedef TypRef& TypInOut; // input and output argument
OPaC sources never use pointers to objects : this helps
reducing the probability of errors related to erroneous pointer
manipulation, null-pointer crashes, memory leakage, etc. Objects
are accessed exclusively through this
or smart
pointers.
Smart pointers (also improperly called references in the OPaC jargon) encapsulate the real object pointers. They provide dynamic type checking, validity checking before method invocation (calls to invalid objects are trapped automatically) and automatic reference counting (which can be used to free objects when they are no longer used).
The smart pointers are all derived from the base class OPObjectRef
,
which provides the following public methods :
null
or to the specified
initial object (including runtime type checking).IsValid
: checks the validity of the
pointer. Typically returns FALSE
if it is a
null pointer.The C++ operator ->
has been overloaded in
order to provide an access to the encapsulated pointer. Thus, the
following statements are equivalent :
// Using normal pointers : OPView* view = GetMyView (); view->Draw (); // Using smart pointers : OPViewRef view = GetMyView (); view->Draw ();
The overhead introduced by the use of smart pointers is rather
small but not totally insignificant : operator ->
has been defined inline
and its validity checking
only requires a simple if (object != 0)
statement.
Modern compilers generate about three to five extra CPU
instructions (note that this is architecture dependent).
OPaC objects cannot be instanciated as local or static
variables. They can only be instanciated dynamically through the
macro OP_NEW
and its variations, the replacement for
operator new
. To ensure that objects are not created
locally by accidental, the constructors of all classes are
declared as protected
. This also removes the risk of
using pointers to objects, since they cannot be allocated.
OP_NEW (OPView);
returns a smart pointer of
type OPViewRef
, which points to an instance of class
OPView
. The allocation is based on the default OPaC
memory zone and is performed by the OPaC class manager,
implemented by OPClassMan
.
No arguments can be transmitted to the object on its construction. This is not considered to be an important restriction, since the constructor should only be used for minimal object initialisation; further initialisation requiring parameters will need dedicated methods.
OPaC objects cannot be deleted explicitly : the memory management is based on automatic destruction and garbage collection. If an object is no longer needed by the application, it will be automatically deleted.
A programmer might still want to delete an object explicitly.
A special method Delete
exists for this
purpose : it prepares the object for its destruction but
does not release the memory yet. The object's memory will be
freed only after the destructor has been called.
The following example compares traditional object allocation and destruction with the model used by OPaC :
OPView* view = new OPView (); ... delete view; view = 0; OPViewRef view = OP_NEW (OPView); ... /* no explicit deletion needed */ view = 0;
Native C++ arrays are not recommended, since they do not
provide any runtime boundary verification. Furthermore, C++
arrays are restricted to a single data type. OPaC provides an OPArray
class which implements the same basic functionality as the native
arrays of smart pointers, plus dynamic re-sizing, runtime type
checking, boundary verification and automatic cleanup on
destruction.
The following example compares native arrays of references with OPaC arrays :
OPViewRef array[3]; array[0] = GetMyView (); array[0]->Draw (); num_elem = sizeof (array) / sizeof (array[0]); /* resizing is not possible */ OPViewArray array (3); array[0] = GetMyView (); array[0]->Draw (); num_elem = array->GetSize (); array->SetSize (5);
The OPViewArray
is a subclass of an OPArrayRef
which implements operator []
returning values
of type OPViewRef&
. The size of the array can be
specified at the construction time (note that parenthesises (3)
are used instead of brackets [3]
) but can also be
changed afterwards by calling SetSize
.
OPaC needs extensive runtime information about the existing classes in order to achieve dynamic message passing and automatic class field browsing. Furthermore, a smart pointer class has to be defined for each class.
The necessary information is provided by a set of macros :
OP_EXPORT (Class, Super);
defines the basic
types (such as ClassRef
or ClassIn
).
Most OPaC classes are declared in file OPaC/opexport.h
(this reduces the need to include the full class
definition when a simple class reference is required).OP_DEFINE_ARRAY (Class, Super);
defines the
basic array type ClassArray
which can be
used to store an array of objects. Each ClassArray
inherits its properties from the base OPArrayRef
smart pointer; class OPArray
implements the
generic array manipulation methods and ClassArray
just provide a wrapper used for type conversion. Most
OPaC arrays are also declared in file OPaC/opexport.h
.Both OP_EXPORT
and OP_DEFINE_ARRAY
should belong into a common header file, for a given project.
This helps reducing recompilation time whenever a class has been
modified. Class definitions often refer to other classes only
through their references, so there is no need to include the full
class definition. This policy is used extensively by OPaC and
most class references are defined in file OPaC/export.h
.
A class referring to another one, for example OPViewRef
,
will not need to include the specific header file (in this case OPaC/view.h
)
to use the references in the header file. Only the implementation
file will need to include it (OPaC/view.h
) if it
calls its methods.
OP_DEFINE_CLASS (Class, Super);
defines the
smart pointer class (the reference class ClassRef
)
and most of its inline methods. It also defines the super
keyword which can be used to reference the super-class.
This macro has to be inserted at the last line of a
standard class definition (see example below).The following example demonstrates the use of the macros that have just been presented :
// Definition of the reference class OPTestRef and of the array // type OPTestArray (and associated OPTestIn, OPTestOut, etc.) OP_EXPORT (OPTest, OPObject); OP_DEFINE_ARRAY (OPTest, OPObject); /* * The class OPTest demonstrates how a simple class can be derived from * the OPaC base class. */ class OP_PUBLIC OPTest : public OPObject { protected: OPStringRef user_name; protected: OPTest (); virtual ~ OPTest (); virtual void StoreInstance (OPStorageIn storage); /* OVERRIDE */ virtual void RestoreInstance (OPStorageIn storage); /* OVERRIDE */ public: virtual Bool SetUserName (OPStringIn name); virtual Bool GetUserName (OPStringOut name) const; virtual void SayHello (); OP_DEFINE_CLASS (OPTest, OPObject); };
The example defines both the constructor and the destructor
as protected
. This is required by OPaC so that no
automatic object can ever be created on the stack. The only
allocation mechanism supported by OPaC is provided by the class
manager (which uses a special construction function provided by
the class definition macros).
Most objects which have associated data provide methods to
store and restore their state, namely StoreInstance
and RestoreInstance
. Each class should call its
super-class methods before doing its own work. Storing and
restoring is as simple as using the traditional C++ iostream
operators <<
and >>
.
The macros used for the class definition do not generate everything that is needed by OPaC for its run-time system. An additional macro needs to be used in the implementation file in order to provide the trampoline functions and static variables needed by OPaC. Depending on the class type, one of the following macros has to be used :
OP_IMPLEMENT_CLASS (Class, Super);
implements hidden methods needed to export runtime
information and provides the services needed for dynamic
object allocation.OP_ABSTRACT_CLASS (Class, Super);
is similar
to the above macro but applies to abstract classes (those
which have purely virtual members and thus cannot be
instanciated).An implementation file typically looks like the following listing :
/* * test.h * * OPaC test class used as a "hello world" sample. */ #include <OPaC/test.h> OP_IMPLEMENTATION (test.h); OP_IMPLEMENT_CLASS (OPTest); OPTest::OPTest () { ... }
In order to make dynamic message passing possible, a set of macros has to be used to define the target methods and their arguments.
A generic dynamic method definition needs quite a few macros to define its interface :
OP_TARGET_CLASS (Class);
starts the
definition of a generic dynamic method (simply called
target in OPaC's vocabulary).OP_TARGET_ARG (Type, name);
defines the
argument name of type Type. This can be
used several times if the target accepts several
arguments. A target without any argument should rather be
defined as an actor, since a single macro suffices.OP_TARGET_CALL (Method (arg1, arg2));
defines how the target Method should be called; it
states the name of the target and lists its arguments arg...
This has to be immediately followed by the OP_TARGET_END
macro.OP_TARGET_END ();
ends the definition of the
target. This is required for historical reasons but might
be dropped in future versions of OPaC.An actor is a very simple, argument-less, target. An observer
is a special target which takes an object of type OPObjectIn
and an ID of type Card32
as arguments. They are
defined using the following macros :
OP_TARGET_ACTOR (Class, Method);
defines the
actor named Method.OP_TARGET_OBSERVER (Class, Method);
defines
the observer names Method.