Untraced OOM-2 objects (in the sense described in section 19.2) are declared and used in almost exactly the same way as are traced objects. They are allowed to have the same kinds of components, and the same things are forbidden. The differences are as follows:
Example: The class in this demonstration allows actions to be timed. When a member of the class Timer is instantiated, the current time is stored. At any time desired, the elapsed time on that timer can be displayed. The code here is only a demonstration, and is not very complete, but it is adequate for a minute/second timer. In this example, the class has a private procedure to calculate a DateTime difference, and a revealed one to invoke this and display the result. The FINALLY clause is there just to demonstrate that it is invoked when the object is destroyed. In practice, this clause would only be necessary if some other memory deallocation had to be done as part of the destruction of the object.
UNSAFEGUARDED MODULE TimeClassDemo; FROM SysClock IMPORT DateTime, GetClock; FROM SWholeIO IMPORT WriteCard; FROM STextIO IMPORT WriteLn, WriteString, ReadChar, WriteChar; FROM Storage IMPORT ALLOCATE, DEALLOCATE; CLASS Timer; REVEAL WriteElapsed; VAR start, elapsed : DateTime; PROCEDURE CalcElapsed; VAR current : DateTime; borrow : BOOLEAN; difference : INTEGER; BEGIN GetClock (current); (* note the necessity to use integer arithmetic on the cardinal fields *) difference := INT (current.second) - INT (start.second); borrow := (difference < 0); elapsed.second := difference MOD 60; (* put back as a cardinal *) difference := INT (current.minute) - INT (start.minute); IF borrow THEN DEC (difference) END; borrow := (difference < 0); elapsed.minute := difference MOD 60; (* repeat for hours, days, and years to finish off. *) (* only enough code here for a minute/second timer *) END CalcElapsed; PROCEDURE WriteElapsed; BEGIN CalcElapsed; WITH elapsed DO (* add more code for years, months, days, hours if desired *) WriteCard (minute, 1); WriteChar (":"); WriteCard (second, 1); END; END WriteElapsed; BEGIN (* initialize *) GetClock (start); FINALLY WriteLn; WriteString ("This timer has been destroyed."); WriteLn; (* well, we could put something useful here, anyway. *) END Timer; VAR time : Timer; ch : CHAR; BEGIN (* main *) CREATE (time); WriteString ("Timer started; press return to see elapsed time "); ReadChar (ch); time.WriteElapsed; DESTROY (time); END TimeClassDemo.
There may be a surprise or two in this code, so it ought to be read over carefully. First, observe that when untraced objects are created and destroyed, the memory management is turned over to ALLOCATE and DEALLOCATE respectively, which must therefore be made visible, as is the case with using pointers with NEW and DISPOSE. These imports did not have to be made for working with traced classes, because the garbage collector is given authority over the relevant memory. Second, note that the module has to be marked UNSAFEGUARDED because it contains an untraced class. This would be the case even if it contained several traced classes as well; the presence of even one untraced class means that the module is unsafeguarded. This is also true if any imports are made from an unsafeguarded module, so safeguarded modules cannot do that either.
When the code was run, and the return key pressed almost immediately, the result was as shown below, verifying the correct action of the destructor:
Timer started; press return to see elapsed time 0:2 This timer has been destroyed.
The reader should note that, although there is little difference between the outward effects of using traced or untraced objects, the efficacy of secure memory management by the garbage collector is lost when untraced objects are employed. It would be advisable therefore, to use only traced objects unless there are compelling reasons to do otherwise. Almost all the remaining examples in this chapter will be of traced objects in safeguarded modules. To drive this point home, consider what happens when CREATE is called more than once on an untraced object reference.
CREATE (thingy); (* some code here, but no DESTROY *) CREATE (thingy); (* again *)
Unlike the situation with traced objects, the garbage that is generated by leaving memory that was allocated to the reference thingy in the first call to CREATE without a valid reference is not collected, and indeed it cannot be, for the garbage collector has never been told to trace objects of such a class. Such garbage will simply pile up until the program runs out of memory and (probably) crashes.
It should also be apparent that it is an error to attempt to use DESTROY on an object of a traced class.