Defining Classes
It's strange how you never know...
But we'd both gotten what we'd asked for...
Such a long, long time ago.
- Harry Chapin
The Class Definition statement defines a new data type which can contain any number of separate data items and any number of methods to operate on the data. A Class is a complex data type which can only be used by dynamic allocation using the 'new<>' keyword. The following table shows the syntax for the class statement.
♦ Classes can only be created with a global scope; a class can not be defined within another class. However, the visibility of a class can be controlled in a variety of ways, with a default visibility of ‘unit’.
♦ The default space is ‘aztec.script’, unless the CompilerSetSpace() method was used to define a different default.
♦ Either way, the class name itself can contain the associated space (e.g. aztec.script.test.ClassName).
♦ The following table shows the keywords that are supported within the class definition statement. The 'class' keyword is the only one that is required.
♦ Classes can contain any number of data items and methods, and the visibility of each member item can be separately controlled.
♦ Data items and methods in the class are accessed using the "." operator when used outside the class itself. When accessed inside methods of the class itself, the member items can be accessed directly.
♦ If the member item is marked as 'shared', member items can be accessed using the class name of an object of that class family (Type.DataItem or Object.DataItem).
♦ If the member item is not 'shared', i.e. it is an instance member, it must be accessed using a valid object reference (Object.MemberMethod()).
♦ The system automatically creates a local variable named 'self' inside every class instance method which returns a reference to the object.
♦ The 'self' reference is particularly useful to access a data item from the class that has the same name as a local variable.
♦ A class can be derived from one or more classes, and the ‘from<>’ keyword is used to list the classes. True multiple inheritance is supported with instance data supported in every class that this new class is derived from. The Inheritance and Polymorphism page discusses this topic in more detail, but the following diagram shows a simple class hierarchy example. In this case, all classes can contain instance data, and although probably not typical, ClassA can exist in each of the hierarchy paths as shown.
♦ Methods with the same name as the class are called constructors, and they can be overloaded (each with a unique argument list). The constructor is called automatically during the new<> call, and the appropriate constructor is determined by the argument list.
♦ If the class name is immediately followed by braces, with one or more embedded classes, it is a special Template class. Refer to the Aztec Class Template page for more details on how to define and use template classes.
♦ When an object is no longer referenced, the Aztec Virtual Machine automatically invokes the object’s destructor to clean up the object, if one is defined. The destructor for a class is defined as the ClassName + “Cleanup”. There are no arguments and there is no return value. A class does need to define a destructor, but it can create one if it makes sense to automatically cleanup a resource when an object goes out of scope. There is no need to do any memory cleanup, since the Aztec Virtual Machine automatically manages the object's memory. However, there are oftentimes other critical resources that may make sense to manage in a destructor.
♦ The VM automatically calls all detructors in an object's class hierarchy, from bottom (NewClass) up to the Base class, in a left to right manner if multiple paths. This occurs regardless of whether the "NewClass" has a destructor or not..
♦ The destructor method is not necessarily responsible for destroying anything; it is really responsible for cleaning up the object before it is destroyed by the VM. It is true though, that the clean up process may in turn cause the destruction of other objects.
♦ The timing and location of the destructor's execution depends on the "fore" versus "back" setting for the object.
♦ If "fore", the destructor(s) are executed in-line within the same Virtual Machine Thread where the object's reference count dropped to zero. They are guaranteed to be executed before the next VM instruction following the location where the object went out of scope. This lays the foundation for "deterministic destructors".
♦ If "back", the destructor is executed by a separate VM thead known as the "Destructor Thread". It is basically a server thread that receives requests to execute destructors of objects in the background. There is only one Destructor Thread in the Aztec Engine, so all background destructor requests from all VM Threads in use by the script/application get queued up, and are executed one at a time by the special Destructor Thread. This is not a deterministic approach to execute a destructor, as it is not run within the same thread (i.e. that thread keeps running in parallel as the object gets queue up and executed in the Destructor Thread), and there is also no guarantee about the timing of one destructor versus another, since they can come in from any thread at any time.
♦ The Aztec Engine overall default memory setting is 'back', and is typically a good way to go, unless there are specific reasons to control object management in the foreground. Background destruction allows the system to queue up the destructors and execute them in parallel with the continuing execution of the VM thread where the object went out of scope. This typically provides a smoother running, more responsive system. However, in some cases it does make sense to use 'fore', especially for specific classes that control critical system resources. In this case, using foreground object destruction provides a flexible and simple solution for deterministically managing the cleanup of objects and the resources contained in them.
♦ The setting can be controlled at three separate levels, ranging from wider to more narrow control, providing a lot of flexibility to control its use.
♦ The overall system default of 'back' can be overidden in one shot using the "-fore" option on the Aztec Engine command line. This makes all object destructors execute in the foreground by default, as described above, rather than sending them to the Destructor Thread. This is not normally recommended, since for most objects that don't require manual control over the destruction, it allows the Virtual Machine as a whole to run more smoothly. It may be interesting to try it out and see how it affects the execution and responsiveness of a particular script/application. In most cases, it will likely not make much of a difference.
♦ The second level of control is to use the setting at a class level. Every object that is created for that class will be marked with this memory handling setting, unless it gets overridden at the 3rd level described below. The class level setting automatically overrides the more general setting from the Aztec command line option (or the overall system default value of 'back'). When it does make sense to use deterministic destructors, this is the level of control that typically makes the most sense. If the class contains one or more system resources such as thread synchronization objects or stream I/O objects, then the 'fore' setting will likely make sense for every instance object of that class.
♦ The third level of control is at a single object level. Regardless of the system wide settings or the class level settings described above, the object level setting overrides everything else for that single object. The 'fore' or 'back' setting can be used directly in the "new< >" call (e.g. new<fore SyncLock>).
♦ The majority of classes in the Aztec Class Framework do use a destructor method. They are actually implemented internally using the C++ Plug-In Framework Interface, so some object level cleanup is typically needed. None of the framework classes are designed to require foreground destruction however, so using background destruction of framework objects (which is the default) is normal and expected. Of course, all framework classes can safely be destroyed in the foreground as well, if it makes sense for the functionality of the script/application. The main point is that the use of framework classes will result in the execution of one or more destructors internally, even if the user code in a script/application does not specifically use destructors.
♦ The Aztec Virtual Machine controls access to every object and automatically cleans up Aztec objects when they are no longer referenced. The Aztec VM does not currently monitor dynamically for objects that reference each other or other circular reference situations where the object reference won't necessarily go away simply by reference objects going out of scope.
♦ This is not necessarily an issue by itself, as the memory itself will ultimately be freed up the Aztec Engine. However, a circular object reference could prevent an object from properly being cleaned up by the Virtual Machine, and if it has a destructor, it may prevent that method from performing its cleanup. The "Base.ObjectCleanup()" method is provided as a simple means for an object to manually cleanup all of its object references, which may result in the proper cleanup necessary to break the circular reference. A "Cascade" flag is also available (false by default), which causes the VM to recursively drill down into each reference and perform similar cleanup. Use the "Cascade" flag with caution.
♦ The ‘new’ keyword is used to dynamically create an object of a specific class.
♦ Syntax for creating new objects
♦ new< [keywords] ClassName()>
# Creates an object of type "ClassName". Parentheses are optional if no arguments.
♦ new< [keywords] ClassName[n1,n2,n3]> # Creates a three dimensional array of type "ClassName"
♦ Valid keywords are ‘fore’ or ‘back’, specifying how the object will be cleaned up, in the foreground (deterministic destructor) or in the background by the destructor thread.
♦ If no value specified, the default value is the setting that the class is defined with.
♦ If none specified there, then the Aztec Run-Time Engine command line setting (-fore or –back) will be used.
♦ If none of those are specified, then ‘back’ is used by default.
♦ The ClassName() term invokes the constructor for the class, with or without arguments as appropriate. Constructors can be overloaded.
♦ Example: data<SyncLock> Lock = new<SyncLock(0)>
♦ Data items marked as ‘shared’ will be shared by all instances of the class, and methods marked as ‘shared’ will only have access to shared data, they will not have access to instance data items.
♦ Definition of a shared data item can provide an initialization expression which the Virtual machine will execute during startup.
♦ For every instance method, there is a predefined local variable named ‘self’ which is a reference to the instance object.
♦ The entire definition of a class can be broken into multiple sections using the ‘satellite’ keyword, and each section can be in a separate module.
♦ The original/primary class must be marked as 'dynamic' in order to support satellite class definitions.
♦ Satellite classes can be used together with visibility options for maximum control over access to data and methods.
♦ New constant or shared data items can be added to existing classes using the satellite. Instance data items cannot be defined inside a satellite class.
♦ New shared or instance methods can be added to existing classes using the satellite.
♦ All classes in the Aztec Class Framework are marked as 'dynamic', so satellite classes can be used with them.
♦ Primitive classes such as 'int', 'string' and enumerations can not have instance methods added - only shared methods can be added.