Next: , Previous: , Up: Top   [Index]


Classes

Defining Classes and Objects

The previous chapters described some of the methods that the Ctalk classes define. This chapter discusses in further detail how to define Classes and variables that belong to the classes, and discusses some of the ways that Ctalk initializes instances of a class.

Many of the methods used in this chapter are primitive methods. The primitive method definitions are in the Object class, and any class can use them.

Superclasses

At a minimum, a class definition defines the name of the new class and its superclass. The primitive method class (Object class) takes the name of a new class as its argument. The new class is defined in the class library as a subclass of the receiver’s class.

This statement defines a class with the name WriteFileStream as a subclass of the receiver, FileStream.

FileStream class WriteFileStream;

Or, more generically,

superclassname class newclassname <docstring>;

This declaration is necessary for Ctalk to identify the file as a class.

The superclassname indicates where the class fits into the class library, and that determines which methods and instance data the new class inherits. The only class that does not have a superclassname is Object, the topmost class in the class library. Object class is loaded by default and doesn’t require a class declaration.

The docstring element is optional; it’s a human readable description of the class. It’s simply text enclosed in double quotes between the classname and the final semicolon. Here’s the previous example with a possible documentation string added.


FileStream class WriteFileStream
"Defines the methods and instance variables that write data to files. Also
defines the class variables stdoutStream and stderrStream, which are the 
object representation of the standard output and standard error streams.";

It’s necessary that the superclasses of the new class already be defined. That statement may seem obvious, but the definitions of class and instance variables that the new class inherits must already be present. That can sometimes be tricky when defining basic classes.

Another reason is that, when you define an instance or class variable, the variable is a member of your new class, unless you specify that the variable belongs to another class. If your class can inherit class variables, instance variables, and methods from the class’s superclasses, then they get used by the new class also. Any new variables and methods only need to be referenced by the class you’re defining.

However, you can direct Ctalk to initialize other classes if your class needs them, by using, for example, statements like require, as described below.

Note: As mentioned above, when dealing with a basic class like Object or Symbol, a method can’t always depend on a class being defined. During initialization, Ctalk programs require a number of basic classes: Object, Symbol, Integer, and ArgumentList, and their required classes Magnitude and Character.

If, for example, you define a method in class Symbol to print objects, you cannot use WriteFileStream class, which needs Symbol and its methods already defined. That would mean Symbol class needs to look up WriteFileStream, which needs to look up Symbol, which needs to look up WriteFileStream again, and so on.

Ctalk doesn’t necessarily stop you from writing programs with circular class references - its method prototyping mechanism can often compensate by checking definitions from different classes as needed, but in cases where Ctalk can’t resolve circular references, then it’s necessary to add methods or instance variables in subclasses.

The previous chapters have described some of the ways that you can work with instance data using C. Many of the Ctalk library’s API functions also check for the type and value instance data. The method API is described later on.

Instance Variables

We mentioned already that all objects have a default instance variable named value. However, many classes define other instance variables. Classes define instance variables with the instanceVariable method.

Here is an example of an instance variable definition.

MyNewClass instanceVariable textMessage String NULL;

This example declares that all instances of MyNewClass will have an instance variable named textMessage, and that it is an instance of class String, and it has the initial value NULL.

The second and third arguments of instanceVariable are optional. Here is the syntax of the instanceVariable method.

classname instanceVariable varname [varclass|typecast_expr] [initialvalue]

Remember that instanceVariable only defines variables. The method does not actually create them. When you create an object that is an instance of your class, the object gets its own copy of the variable.

If the program declares a variable with a type cast instead of a native class for the variable, Ctalk matches the type cast to a class. If the type cast of the variable is a pointer to some data type that is not commonly used in Ctalk or C programs, then Ctalk simply translates the native type to Symbol. This is because the program needs to implement its own methods for handling the data, and Symbol is the most generic class that Ctalk can use for referring to C data in memory.

Class Variables

On the other hand, the classVariable method creates the single copy of a class variable that the instances of a class (and most other classes) can use. Creating a class variable is similar to creating an instance variable.

MyNewClass classVariable systemError Integer 0;

For completeness, here is the classVariable method’s syntax.

classname classVariable varname [varclass|typecast_expr] [initialvalue]

Remember that if you define a variable as a different class than its member class, then the variable responds to messages of its own class. In these cases, you need to take care that you are working with the variable, and not the class that it belongs to.

If the variable’s native type is declared with a type cast, then Ctalk tries to translate the type cast into a Ctalk class, as described in the previous section.

Class Initialization

Although you can define an initial value for a class variable, in many cases you need to wait until the program is run before setting the actual value of a class variable. An example of this is stdoutStream (class WriteFileStream).

In many cases, a constructor method (which in Ctalk are named new), can call a class initialization method.

Here is a hypothetical class with classInit a method. This example simply sets the the name of the system in a class variable called hostName.


MySuperClass class MyClass;

MyClass classVariable myClassVar String NULL;

...
Other class and instance variable definitions.
...

MyClass classMethod classInit (void) {

  OBJECT *classObject_object, 
    *classInitVar_object, 
    *myClassVar_object;
  char buf[MAXLABEL];

  /*
   *  Return if we've already initialize the class.
   */
  if (self classInitDone)
    return NULL;


  /*
   *  Retrieve the class object, because we'll need it below.
   */

  classObject_object = __ctalkGetClass("MyClass");

  /*
   *  Look for myClassVar.  
   *  The third argument of __ctalkGetClassVariable instructs the 
   *  function not to print a warning if it doesn't find the 
   *  variable.  If the variable is not present, then the class
   *  is not completely initialized, so return.
   */

  if ((myClassVar_object = 
    __ctalkGetClassVariable (self, "myClassVar", FALSE)) == NULL) {
    return NULL;
  }

  ...
  Initialize myClassVar here.
  ...

  classInitVar = 
    __ctalkCreateObjectInit ("classInitDone", "Integer",
                             "Magnitude", GLOBAL_VAR, "1");
  __ctalkAddClassVariable (classObject, "classInitDone", classInitVar);

  return NULL;
}

...
Other methods.
...

When writing classes, you need to remember that Ctalk initializes the class and its methods and data in the following order.

  1. The class itself and its superclass.
  2. The class’ instance and class methods.
  3. Instance variables.
  4. Class variables.

Remember that a class definition only requires a superclass. All of the other elements are optional.

It is possible, when initializing complex classes, to construct objects before the class is completely defined. That’s why classInit above checks for the class variable myClassVar and returns if it is not present.

However, Ctalk checks the class initialization every time it creates an instance of MyClass, so a constructor method for MyClass might look like the following.

MyClass instanceMethod new (char *myObjectName) {

  MyClass super new myObjectName;

  __ctalkInstanceVarsFromClassObject (myObjectName);

  MyClass classInit;
  
  return myObjectName;
}

The program then initializes the class variables when the class is completely defined. If classInitDone is TRUE, then the program won’t initialize the class again.

Note: Ctalk does not automatically create a copy of a class’s instance variables when creating objects. Your program must call __ctalkInstanceVarsFromClassObject to create the instance variables in the new object.

If you simply want to initialize a class without creating a new object, you can add a statement like this to your program.

MyClass classInit;

Another example of a method that performs class initialization is the method initDigits (class ClockDigit) in the program ltime.c. The method is too long to list here, but you can find ltime.c in the Ctalk distribution’s demos subdirectory.

The later chapters describe methods and library functions that perform class initialization in more detail.

require Keyword

Ctalk programs initialize the basic classes before starting to initialize the application’s class and method definitions. However, if your definition needs a class that Ctalk does not automatically include, you can initialize the class and include its method and variable definitions before defining your own class by using the keyword require.


require Boolean;

This can be useful if you write a class and Ctalk outputs a message like this one.


prefix34.c:13: Method raiseException," (class Exception)
     used before it is defined.

In that case, you might want to add ‘require Exception;’ at the beginning of the class.

If that doesn’t help, another tactic is to add a method to the new class that duplicates the function of the yet-to-be- defined method. Yet another tactic is to create a unique subclass that contains the method you need.

One issue is that the superclass of require's operand (in the example above, that’s Boolean's superclass, Object) is already completely defined, or the definitions in the input file that contains the require statement can cause circular class references.

However, these errors occur during compilation. If you can write a method that does what you want at run time, then that may be another yet strategy that you can use. For example, if you write an expression like this in a basic class like Symbol, you’ll get “Method used before definition” errors.


/* The reference to Key class requires methods that aren't defined yet. */

if ((arg is Symbol) || (arg is Key)) {  
   ...
}

Instead, Object class contains the method isSubClassOf, which doesn’t require the compiler to determine the class of arg beforehand, because the method evaluates the class of ‘arg’ at run time.


if (arg isSubClassOf "Symbol") {

   ...

}

It’s useful for Ctalk to limit additions the basic classes. You can add a method to an existing class by defining the method in a source file (which is evaluated after all of the basic classes are evaluated), but you can’t add another library file to an existing class. This requires more planning when adding subclasses and methods, but it also makes the compiler trimmer.

Where Ctalk Looks for Class Libraries

When compiling programs, Ctalk looks for class libraries in the following order.

  1. Directories named as arguments to the -I command line option.
  2. Directories named by the CLASSLIBDIRS environment variable.
  3. Ctalk’s directory for the standard libraries.

Ctalk applications occasionally need to look up a class library when they are run. In that case, the program searches directories in the following order.

  1. Directories named by the CLASSLIBDIRS environment variable.
  2. Ctalk’s directory for the standard libraries.

The directory where the standard libraries are located is determined when Ctalk is built and installed. This directory defaults to /usr/local/include/ctalk on UNIX systems. For more details, refer to the entry for the __classLibraryPath() function in the language reference.

Ctalk expands directories to their fully qualified names, so if you build a program with a command like this:

$ ctcc -I . myprogram.c -o myprogram

Then Ctalk expands the ‘.’ to the full path of the current directory, and searches for class libraries in the current directory in addition to the directories it normally searches.

In addition, for each search directory, if a subdirectory named ctalk exists, then Ctalk searches that subdirectory also.

The classSearchPath method, defined in Application class, returns a String that contains the class search path. This example prints the search path, with each directory separated by a colon (‘:’).

int main () {
  Application new app;
  printf ("%s\n", app classSearchPath);
}

The ‘--printlibdirs’ command line option prints the directories that Ctalk searches, one directory per line.

Instance Variables and Method Overloading

One consequence of all this convenient instance data and overloaded methods is that it is easy to confuse which method a message actually translates into. That is because the member class of an instance or class variable might not be the class of the variable’s own instance data.

Here is an example. The openOn message in the first statement translates to the openOn method in the ANSITerminalPane class, while the openOn message in the second statement translates to the openOn method in the ReadFileStream class.

ANSITerminalPane new mainWindow;

mainWindow paneStream openOn "/dev/ttyS0";               /* Correct.  */

mainWindow paneStream inputHandle openOn "/dev/ttyS0";   /* Incorrect. */

This is because inputHandle in the second statement is actually a ReadFileStream, while the program actually wants to call is the openOn method from ANSITerminalStream, which is the effect of the first statement. The Stream classes have many classes in common. In most cases this makes things simpler, but not always.

There are several ways to work around this issue. This first is to use stack walkbacks and exception handlers whenever a program opens a file. That allows you to see what methods the program’s statements actually translate into. See Debugging.

If you can’t avoid an incorrect message translation in a program, you can write your own method that only one of your program’s classes responds to. For example, instead of yet another openOn method, your program might define a method like, for example, openOnInputData.

Finally, if the program cannot determine in advance which class is going to receive a message, you can write a method that dispatches the receiver to a method of the correct class. Here is an example.

Stream instanceMethod openOnTerminal (String pathName) {
  Exception new e;
  if (self is ANSITerminalStream) { 
    if (pathName isATty) {
      self openOn pathName;
    } else {
      e raiseException NOT_A_TTY_X, pathName;
    } 
  } else {
    e raiseException INVALID_RECEIVER_X, pathName;
  }
  return self;
}

Next: , Previous: , Up: Top   [Index]