<< Prev | - Up - | Next >> |
In this chapter, we are going to generalize the counter idea: instead of having just one global counter, we are going to have counter objects implemented as extensions. Of course, this is intended purely as a didactic exercise: such counters could much more easily be defined as Oz objects directly.
We derive a new class
Counter
class from the base class class
OZ_Extension
.
#include "mozart.h"
class Counter : public OZ_Extension {
public:
long * n;
Counter();
Counter(long*);
static int id;
virtual int getIdV();
virtual OZ_Term typeV();
virtual OZ_Extension* gCollectV(void);
virtual OZ_Extension* sCloneV(void);
virtual void gCollectRecurseV(void) {}
virtual void sCloneRecurseV(void) {}
virtual OZ_Term printV(int depth = 10);
};
A class
Counter
object contains a pointer to a malloced long
. Why not simply have a member of type long
: simply because we want to illustrate an application of finalization; malloced memory is a resource that needs to be freed when no longer needed.
The class
Counter
class provides implementations for a number of virtual member functions introduced by class class
Oz_Extension
. We are now going to explain each of them and provide the necessary code.
For this we need the Counter()
constructor and the new builtin counter_new
. The constructor allocates a new long
, sets n
to its address and initializes it with 1. The builtin creates a new instance of the class
Counter
class, boxes it as an Oz value by invoking OZ_extension
and returns the result.
Counter::Counter() { n = new long[1]; n[0]=1; }
OZ_BI_define(counter_new,0,1)
{
OZ_RETURN(OZ_extension(new Counter));
}
OZ_BI_end
Every extension class should be uniquely identified. This is the purpose of virtual function getIdV
. Here we illustrate the usual way of doing so: the class is equipped with a static id
member and getIdV()
returns it. This static member is initialized by oz_init_module()
(see Section 16.9).
int Counter::id;
int Counter::getIdV() { return id; }
Your code will also need to test whether some OZ_Term
is in fact a boxed class
Counter
.
inline OZ_Boolean OZ_isCounter(OZ_Term t)
{
t = OZ_deref(t);
return OZ_isExtension(t) &&
OZ_getExtension(t)->getIdV()==Counter::id;
}
Additionally, you should probably provide a builtin to perform this test in Oz code:
OZ_BI_define(counter_is,1,1)
{
OZ_declareDetTerm(0,t);
OZ_RETURN_BOOL(OZ_isCounter(t));
}
OZ_BI_end
Finally, it would be nice if {Value.type C}
would return the atom counter
when C
is a counter object.
OZ_Term Counter::typeV() { return OZ_atom("counter"); }
Obviously we need a way to unbox counter objects.
inline Counter* OZ_CounterToC(OZ_Term t)
{
return (Counter*) OZ_getExtension(OZ_deref(t));
}
Now we can define a convenient macro that we can use in the implementation of a builtin to wait until argument ARG
is determined, check that it is a boxed class
Counter
, and declare a variable VAR
to hold a pointer to its unboxed value.
#define OZ_declareCounter(ARG,VAR) \
OZ_declareType(ARG,VAR,Counter*,"counter",OZ_isCounter,OZ_CounterToC)
Next, we illustrate how to use this macro.
The first operation obtains the current value of the counter object, but does not change it. We use our new macro to state that the first argument (i. e. argument number 0) should be a determined boxed counter and that c
should be set to point to its unboxed value.
OZ_BI_define(counter_get,1,1)
{
OZ_declareCounter(0,c);
OZ_RETURN_INT(*c->n);
}
OZ_BI_end
Thanks to our macro, if the argument is not determined, the builtin will automatically suspend, and if it is determined but is not a counter object, it will raise an error exception.
We can similarly define a builtin for setting the value of the counter. It takes 2 arguments: a counter object and an integer.
OZ_BI_define(counter_set,2,0)
{
OZ_declareCounter(0,c);
OZ_declareInt(1,i);
*c->n=i;
return PROCEED;
}
OZ_BI_end
Finally, we can define a builtin to obtain the current value of a counter object and post increment the counter by 1.
OZ_BI_define(counter_next,1,1)
{
OZ_declareCounter(0,c);
long i = *c->n;
*c->n = i+1;
OZ_RETURN_INT(i);
}
OZ_BI_end
Of course, it would be nice if {Show C}
, when C
is a counter object, would display <counter
n>
where n is the current value of the counter. This is easily achieved by defining virtual function printV
to return an appropriate virtual string.
OZ_Term Counter::printV(int depth = 10)
{
return OZ_mkTupleC("#",3,
OZ_atom("<counter "),
OZ_int(*n),
OZ_atom(">"));
}
An instance of an class
OZ_Extension
class lives on the heap and must be properly copied at each garbage collection. This is realized simply by creating a new instance (automatically allocated on the to heap) and initializing it with the appropriate info. In the case of a counter object, we must copy the n
pointer. For this purpose we define a one argument constructor.
Counter::Counter(long*p):n(p){}
OZ_Extension* Counter::gCollectV() { return new Counter(n); }
Cloning is a kind of copying used during search rather than garbage collection. Every variable and every data-structure that has token equality (rather than structural equality), e. g. OZ_Extension
, is situated in a space: its home space, i. e. the computation space in which it was created. When its home space H is cloned, the data-structure D must also be cloned: the clone of D must be situated in the clone of H. In the present case, for simplicity we only intend to support counters at top level; thus, the sClone
method should never be used:
OZ_Extension* Counter::sCloneV() { Assert(0); return 0; }
When all references to a counter object disappear, we would like the malloced long
to be freed. We cannot easily register a counter object for finalization from the C++ code (this will have to be delegated to Oz code), but we can provide the implementation of the finalization handler.
OZ_BI_define(counter_free,1,0)
{
OZ_declareCounter(0,c);
free(c->n);
return PROCEED;
}
OZ_BI_end
We must now package this library as a native functor. This is done by providing the function oz_init_module()
which returns a table of builtins. Here, it must also initialize the static member Counter::id
.
OZ_C_proc_interface * oz_init_module(void)
{
static OZ_C_proc_interface table[] = {
{"new",0,1,counter_new},
{"is",1,1,counter_is},
{"get",1,1,counter_get},
{"set",2,0,counter_set},
{"next",1,1,counter_next},
{"free",1,0,counter_free},
{0,0,0,0}
};
Counter::id = OZ_getUniqueId();
return table;
}
Assuming the code above is put in file counter-obj.cc
, we first compile and then create a DLL as follows
oztool c++ -c counter-obj.cc
oztool ld counter-obj.o -o counter-obj.so-`oztool platform`
The counter object native library will now be wrapped in an Oz module that registers every new counter object for finalization.
functor
import
CNT(new:NEW is:Is get:Get set:Set next:Next free:Free)
at 'counter-obj.so{native}'
Finalize(guardian)
export
New Is Get Set Next
define
Register = {Finalize.guardian Free}
proc {New C}
{NEW C}
{Register C}
end
end
<< Prev | - Up - | Next >> |