Tclpp: An Object-Oriented Extension to Tcl

Version 2.0

Stefan Sinnige

Jun 28, 2000

Copyright © 1999-2000, Stefan Sinnige. See the LICENSE file in the distribution for the complete license and copyright agreement.

Table of Contents

1 Introduction
1.1 Context of Tcl
1.2 Structure of this manual
2 Quick Overview
2.1 Procedural versus Object-Oriented
2.2 Classes and Objects
2.3 Classes as types
2.4 Implicit object naming
2.5 Inheritance
2.6 Virtual Functions
3 Classes
3.1 Introduction
3.2 Variable Declaration
3.3 Procedure Definition
3.4 Construction and Destruction
3.4.1 Constructors
3.4.2 Destructors
4 Inheritance
4.1 Introduction
4.1.1 Construction and Destruction
4.2 Virtual Functions
4.3 Multiple Inheritance
5 Information
5.1 Version Information
5.2 Class Information
5.2.1 Defined classes
5.2.2 Class heritage
5.2.3 Class Variables
5.2.4 Class Methods
5.3 Run-Time Type Information
5.3.1 Class of an Object
5.3.2 Object is a class
6 Scoping
6.1 Basic Scoping
6.2 Using Namespaces
7 Reference Manual
8 Compatibility

1 Introduction

Pure Tcl consists of procedure and variable definitions and is basically a procedural language: the execution of an application can easily be monitored by following the single execution thread. Although this concept is suitable Like the rising of the OOP languages has proven that object oriented languages are a better choice for medium to large scale sized applications. The foundation of the object oriented programming is the notion of classes and objects. Each class declares the layout (the data members) and the behaviour (the procedures) of its objects. Each object is a stand-alone entity, which can be manipulated by invoking its procedures. There is no clear thread of execution, it is basically abstracted to creation, deletion and alteration of objects. For example, a class can declare a car: how it looks like, how to drive, etc. Each instance (object) of a car defines an actual car. It defines the colour, number of doors, how to steer it, its current speed etc. All can be changed and manipulated on a per object basis. Many extensions to Tcl already exist which implement object orientation, among which [Incr Tcl] is the best-known one. The extension presented here implements the object oriented principles in a slightly different way. Its aim is to stay close to the C++ notation concerning inheritance, constructors/destructors and class declaration. This makes the extension more accessible for C++ programmers than any other Tcl OOP extension.

Although Tclpp is implementing a different principle than pure Tcl is using, it is still based on top of the Tcl grammar and syntax. In fact, as figure 1 shows, it is built directly on the Tcl core. All functionality of Tcl is present to the applications as well as the additional functionality of Tclpp.

Figure 1. Tclpp in the Tcl context.

The same similarity can be drawn between C and C++. Although C++ is implementing the object-oriented principles, it is still based on C and both can be used at the same time.

This manual serves two purposes in one volume. It serves as a user manual for those wanting to use the Tclpp as it is, and for those who wish to use the API provided by the C interface to the Tclpp. Furthermore, it will serve as a reference manual to quickly lookup the usage of certain features and to find the definite answer on vague and discutable issues that might arise when using Tclpp.

2 Quick Overview

This section presents a very high level overview of Tclpp to feel acquainted with the concept, features and syntax. Almost all aspects will be examined, but not in full detail. That will be done in later chapters.

There are various techniques for programming, each having its own advantages and disadvantages, but no one is better than the other. They just serve different purposes. Languages using the procedural paradigm, where a program structure is based on the functions it provides (like C, Fortran and Tcl), were popular in the 70s and 80s and are still commonly in use. However, over the years, a change was noticable. The focus was no longer laid on the functions, but more on the data organization of a program. A program was seen as a collection of data on which operations are performed to establish the desired effect. Data became more organized (data-abstraction, data-hiding) into modules and each module provided a certain well-defined interface to alter the data it contained. Witness the birth of the object-oriented programming languages.
Tclpp uses the object-oriented paradigm to bring more structure to your Tcl applications. The data can be better organized into classes and you can declare a well-defined interface to the operations that are allowed on your classes. Each class can be seen as a "type". Just as you can define operations on an integer (which do not apply to, say, a string), so you can do the same with classes. This data-abstraction is the basic rule in object-oriented programming. You define the types by specifying the classes which make up your application and you create the intercommunication between these classes.

Figure 2. Classes and objects.

It is important to understand the difference between classes and objects. A class merily declares a type (by defining the data it contains and the operations that are allowed on that data) but does not define any data so far. It is abstract. An object, or an instance of the class, actually defines the data by allocating space for the data members declared in the class declaration. Of course, a class can have more than one instance, each separated completely. This means that a class declaration does not allocate any space, it only says that any object instantiated from that class must define (allocate) which kind of data and what operations are allowed on that data.

A class declares data and the operations that are allowed on that data. These two aspects are represented in Tclpp by a class. Note that the class does not allocate any memory for data members, that is done when an object of that class is instantiated. Take for example a class representing a circle:
	class Circle {
		#
		# Constructor and Destructor
		#

		Circle {x y r} {
			set x_      $x
			set y_      $y
			set radius_ $r
		}

		~Circle {} {
			Undraw
		}

		#
		# Operations
		#
	
		proc Draw {} {
		}

		proc Undraw {} {
		}

		#
		# Data Members
		#

		variable scalar x_
		variable scalar y_
		variable scalar radius_
	}
It defines four operations and three data members. The data members are declared by the variable keyword, followed by the type and the name of the variable. There are two predefined types, the scalar, representing a Tcl string, list or number and an array, representing a Tcl array. Furthermore, the type can be any class name you define. The variable names all end with an underscore to distinguish them clearly from any other variable in the class operations, but that is not required.
The four procedures defined in the class are actually two special procedures and two normal class procedures. A class may define two special procedures, a constructor and a destructor. The constructor is called whenever an object is instantiated from the class, and the destructor is called whenever an object is deleted. They both should have the same name as the name of the class, except for the destructor which is prepended with a ~ symbol.
Note that all the data members of a class are automatically available in all the class operations. This concept is basically different from Tcl (and many other object oriented extensions) where the variables are explicitely imported in the class procedures when needed.
Now that a class has been declared, objects can be instantiated from it. An object only contains allocated class data members on which the class operations can perform their actions to. To create a circle object from our example class at position (10,20) with a radius of 5:
	Circle circle1 10 20 5
This will define an object, named circle1 with the specified properties. The constructor will be invoked automatically. All that is allowed on this object is to draw it, by calling the draw function:
	circle1 draw
Similar, a second, a third or a billionth circle can be created in the same way. Objects can be deleted as well, which will result in deallocation of the memory space and releasing the object name:
	circle1 delete
or by using:
	delete circle1
After an object has been deleted, it cannot be accessed anymore. During the deletion of the object, the destructor (if defined) will be invoked automatically, which in our example will un-draw the circle.

    2.3 Classes as types

Tclpp supports two build-in variable types, the scalar and the array. The scalar type is just your basic Tcl string (which can represent a list as well of course) and the array is the Tcl array type. However, since classes actually define a type as well, classes can contain objects of other classes as data members as well. For example, if we have a class which consists of two Circle classes:
	class TwoCircle {
		# 
		# Operations
		#

		proc draw {} {
			circle1_ draw
			circle2_ draw
		}

		variable Circle circle1_
		variable Circle circle2_
	}
These variables are available in any class operation procedure, just like the build-in types.

    2.4 Implicit object naming

The object allocated in the previous section is said to be explicitly named: the user provided the name for the object. In many cases, the user is not interested in the exact object name (certainly if the user has to manage a billion circles). In those cases, Tclpp can implicitly pass a name to the user upon creation:
	set object [new Circle 10 20 5]
This object can be referenced just like an explicitly named object, but with the extra indirection to its actual name:
	$object draw
Be aware that if the object cannot be referenced anymore, there is no way to access the object and no way to delete it, resulting in a memory leak. In the following example, the first object cannot be referenced anymore after the second object is created:
	set object [new Circle 10 20 5]
	$object draw
	set object [new Circle 20 30 8]		;# Memory leak: First object 
						;# cannot be referenced 
						;# anymore !
Memory leaks are generally hard to detect and should be avoided at any time.

    2.5 Inheritance

Now that there is a class for a circle, we can define classes for other shapes as well, like squares, rectangles and bezier curves. But, they all share a commonality: they are all shapes. Inheritance allows the user to define such a Shape base class and let every specific shape derive from it. A drawing application is probably only interested in the Shapes and not in the actual specific shape. It will probably only want to do common actions on the shapes, like moving and rotating. These common actions do not have to be defined for all the specific classes, but can be abstracted to the Shape class:
	class Shape {
		proc move {deltax deltay} {
		}

		proc rotate {x y degrees} {
		}
	}

	class DrawingEditor {
		variable scalar shapes 	;# List of shape object names
	}
The DrawingEditor class maintains a list of Shape objects and can only invoke the operations defined by the Shape class.
The derived classes inherit all the data members and procedures of all their base classes, just as if they defined it themselves. This means that the Circle class can invoke the move and rotate functions as well:
	class Circle : Shape {
	}
Now that we defined a Circle and Rectangle to be of type Shape as well, the DrawingEditor can list objects of these types Circle to its list of shapes as well.

    2.6 Virtual Functions

Some inherited functions defined by the base classes do not exactly what is required. In those cases, the user wish to define a common procedure in the base class, but override it in certain derived classes when necessary. For example, drawing a rectangle involves other operations than drawing a circle, but a shape is known to be drawable. In those cases, the base class defines the function as virtual, meaning that a derived class may override its functionality:
	class Shape {
		virtual proc draw {} {
		}
	}

	class Circle : Shape {
		proc draw {} {
			# Drawing a circle
		}
	}

	class Rectangle : Shape {
		proc draw {} {
			# Drawing a rectangle
		}
	}
The drawing editor can now call any draw function on any Shape without knowing which shape it actually draws! If the shape object is in fact a circle, it will draw a circle; if it is a rectangle, it will draw a retangle.

3 Classes

This chapter describes the main facility provided by Tclpp, the notion of classes and objects. All aspects will be covered, from class creation and variable declaration to object usage and calling mechanisms. The following chapter will extend this with inheritance features.

    3.1 Introduction

Object oriented programming as presented by Tclpp introduces the notion of classes and objects. A class encapsulates data and associated procedures into one block. A class on itself is an abstraction and cannot be used as such. Only class instantiations, or objects, contain the actual data on which the class procedures may operate on. They are the implementation of the class by allocating memory to hold the class data. Each object is independent of any other object and its data can only be changed or accessed through that object.
Each class defines a new type, just like the build-in types of Tcl (a string and an array). However, it is more clear what data they contain and what operations are permitted than is the case with the build-in types. All these types can be used within other classes as well as a data member for other class definitions.
A class is defined by the class keyword, the name of the class and its definition of procedures and data. The minimal class definition only defines a type without any data or procedure:
	class Minimal {}
but is not very useful.
When a class is defined, it can be used to create objects from it. Each object has its own name and each object works independently from other objects, beacuse each maintain their own set of data. An object is instantiation by specifying the class and the name of the object:
	class Example {
		....
	}

	Example ex1
	Example ex2
The code fragment above defines two objects, ex1 and ex2 each maintaining their own set of data.

    3.2 Variable Declaration

A class may declare variables, that is, it assigns a name to a type but does not actually define them, ie. allocate memory for it. That will only be done when a class is instantiated. Two types are predefined to the Tcl types: scalar and array and cannot be used for any other purpose. The following class defines four variables, two build-in types and two class types:
	class Example {
		variable scalar name		;# build-in type
		variable array  properties	;# build-in type
		variable Date   date
		variable Time	time
	}
These variables are only accessible through the class defined procedures.
A special scalar variable, named this, is always present and gets allocated and initialized during object instantiation. It refers to the actual name of the object and is accessible only at the class procedures, just like any other type. This type cannot be redeclared in the class definition:
	class Example {
		variable scalar this		;# Error
	}

    3.3 Procedure Definition

A class procedure is just your basic Tcl procedure with only one exception. All data members defined in the class are imported automatically to each procedure and need not be re-imported as is the case with all other (global) variables:
	class Example {
		proc setProperty {key value} {
			set property_($key) $value
		}

		proc display {} {
			puts "Object '$this' has the following properties:"
			foreach key [array names property_] {
				puts "$key -> $property_($key)"
			}
		}

		variable array property_
	}
Each class procedure can be invoked per object and only affects the data of that object. Tclpp takes care of the mapping of the class defined variables and their actual representation in the objects. The syntax is like any other Tcl procedure call, only it is prepended with the name of the object:
	ex1 setProperty shoesize 42
	ex1 display
This will set a property on the ex1 object and displays the result. It will the data of ex2 unaffected of course.
Invoking a class procedure from another class procedure do not require any object identification. However, Tclpp 1.x required the this variable to be prepended to such calls. From Tclpp 2.0 onwards this is not required anymore, but is still correct (from backwards compatibility point of view):
	class Example {
		proc function-1 {} {
		}

		proc function-2 {} {
			function-1		;# Ok (Tclpp 2.0 onwards)
		}

		proc function-3 {} {
			$this function-1	;# Still Ok
		}
	}
Tclpp maintains a calling context to keep track of the objects when class procedures are invoked. At all times it knows which object belongs to such a procedure call, without specifying the object through the this variable.

    3.4 Construction and Destruction

Classes can only be defined once and stay defined until the end of the application. A class can never be redefined or altered at any time.
Objects are created and deleted all the time. They represent the volatile parts of the object-oriented programming principle. Data can easily be created and deleted at any time. Objects can be created in two ways. The first is when the user specifies the name of the object, while the second approach will create an object whose name is chosen by the system:
	Example ex1			;# User provides name
	set ex2 [new Example]		;# System provides name
In the latter approach, the name gets assigned to a variable. Be aware not to re-assign that variable before deleting the object it refers to, otherwise you will introduce a memory leak: the object can not be referenced anymore, but still holds the allocated memory.
An object can be removed by calling the predefined delete function on the object. For example, to delete the two objects created above:
	ex1 delete
	$ex2 delete
which is equivalent to
	delete ex1
	delete $ex2
Note that when an object is created by the latter approach, an extra indirection is required in order to get to the name of the object itself.

      3.4.1 Constructors

Whenever an object is constructed, a special function can be invoked to initialize the object. This function, the constructor, must be supplied by the user and has the same name as the name of the class. It may contain any Tcl arguments which can be passed at creation time:
	class Example {
		proc Example {aName} {
			set name $aName
		}
		variable scalar name
	}
	Example ex1 "An Example"
This code fragment shows the constructor which gets initialed at construction time with the name An Example.
Not only the objects you define explicitely will invoke any constructor, also the instantated class variables will call their constructor. However, since you cannot specify parameters to these type of constructors, they will try to invoke the constructor without pasing any arguments. Be aware that an error is generated when the constructor does not accept no parameters in these cases:
	class Date {
		proc Date {day month year} {
		}
	}

	class Schedule {
		variable Date date	;# Error, will invoke the
					;# constructor of Date without
					;# any parameters upon creation
	}
In such cases, supply default values for every parameter or use the args construct. The constructor of all the class variables will be invoked before the constructor of the containing class.

      3.4.2 Destructors

As a constructor is used for initialization during the construction of an object, so is the destructor invoked when an object is deleted. The destructor has the same name of the class but is prepended with the ~ (the tilde-symbol) and does not accept any parameters. Furthermore, the destructor is called for every class variable as well, just like the constructor did the opposite:
	class Appointment {
		proc ~Appointment {} {
			puts "Appointment deleted"
		}
	}
	class Schedule {
		variable Appointment appointment
	}
	Schedule s1
	s1 delete	;# Calls destructor of the class variable
			;# Schedule::appointment as well		
The destructor of all the class variables will be invoked before the destructor of the containing class.

4 Inheritance

Inheritance is a very flexible and powerful way to extend classes by building up a hierarchy of classes. A class inherits all the variables and procedures of all its parent classes automatically. This enables the user to construct a class having the commonality of the derived classes.

    4.1 Introduction

There are many views on inheritance: some use the approach of encapsulating common code into a class, others share the perspective of creating a specialization of a parent class. But it all boils down to the same thing: a parent class (Tclpp uses the C++ semantics and calls it a base class) implements variables and procedures which define common code to all the child classes (from now on known as derived classes).
For example, take the class representing a person. A person has a name, date of birth, an address etcetera. Such a (base) class may look like:
	class Person {
		proc setName    {name}    { .... }
		proc getName    {}        { .... }
		proc setAddress {address} { .... }
		proc getAddress {}        { .... }
		
		variable scalar name
		variable scalar address
	}
A specialization of this base class can be an employee. It inherits everthing of the Person class, because an employee is-a person. But it has some extra features, specifically defined for an employee:
	class Employee : Person {
		proc setFunction {function} { .... }
		proc getFunction {}         { .... }
		proc setSalary   {amount}   { .... }
		proc getSalary   {}         { .... }

		variable scalar function
		variable scalar salary
	}
The employee inherits everything from the base class Person. Its notation is right after the name of the class, followed by a colon and a list of one or more base classes. The derived class inherits all the class variables and the procedures of its base classes, just like it has defined them itself. This means, that the user may invoke the class operations of Person on an Employee object as well.

      4.1.1 Construction and Destruction

Constructing and destructing an object in an inheritance environment is somewhat more complex than a simple object. Basically, during construction all the variables and procedures of all the base classes exist in the derived class as well as shown in figure 3

Figure 3. Inheritance class layout.

The green class represents the base class. It is extended by the derived blue class with some extra data and operations. The blue class on itself serves as a base class for the pink class, which extends it even further with data and operations.
This hierachy of classes is typical for inheritance, just like the lineage of any person. But it may also be the source of many misunderstanding and erroreneous classes if not designed correctly.
When a derived class is constructed, it will first construct all the base classes before it will construct the derived class. This includes the allocation of the class variables and the calling mechanism of the constructors. Destructing an object is performed in the reverse order. First the derived class is deleted before the base classes.

    4.2 Virtual Functions

When designing base and derived classes, it becomes convenient to define a set of procedures in the base class which can be overwritten by the derived classes. This extends the notion of specialization even further. For example, a shape can have many forms, but how a shape actually looks like depends on the type of the shape. A rectangle is drawn differently than a circle. This means that although a shape has a function to draw it, the actual drawing procedure is accomplished by the specialized (derived) shapes. Defining a function a virtual in the base class enables the derived class to override it:
	class Shape {
		virtual proc Draw {} {
			error "Cannot draw a shapeless form"
		}
	}

	class Rectangle : Draw {
		proc Draw {} {
			# Procedure to draw a rectangle
		}
	}

	class Circle : Draw {
		proc Draw {} {
			# Procedure to draw a cirle
		}
	}

	class Manager {
		proc Draw {} {
			foreach shape $shapes {
				$shape Draw
			}
		}
		variable scalar shapes
	}
The Manager class maintains a list of Shapes. It is not interested in the exact nature of the shape, as long as it is a drawable shape. When the Manager invokes the function Draw, it will invoke the most-derived Draw function. If the shape was a Rectangle, it will invoke the Rectangles function, if it was a Circle, it will invoke the Circles function.
A virtual function can be overriden by a whole chain of derived classes, as long as each of them defines it as virtual as well. In our example, the Rectangle and Circle did not define the function as virtual, such that derived classes from the Rectangle and Circle are not allowed to override this function any further.

    4.3 Multiple Inheritance

A class may have more than one base class at the same time by specifying more than one base class after the colon in the base class list. In contrast to single inheritance, this is called multiple inheritance. Although it is practically not much different from single inheritance, it is more tricky for the underlying machinery to deal with multiple inheritance. The classic example of difficulty with multiple inheritance is inheritance from the same base class:
	class Animal {
	}

	class Male : Animal {
	}

	class Female : Animal {
	}

	class Hermaphrodite : Male Female {
	}
The Hermaphrodite is both a male and female animal, but this implies that it is two Animals. In some designs this is preferred, but in other designs it is not, then it would have been preferred to have two different instances for each base class. To solve this issue, Tclpp always assumes that both are the same base classes and takes the diamond-shaped approach (See figure 4 ), which is not what is desired in all situations but it eliminates many ambiquities.

Figure 4. Diamond Shaped Inheritance.

This means that Tclpp always has so-called virtual base classes. In contrast to C++ where base classes can be virtual and non virtual to represent both the diamond shaped figure and the layout with multiple base classes of the same type.

5 Information

This chapter describes the methods provided by Tclpp to query all kinds of information from classes and objects.

    5.1 Version Information

Information about the version of Tclpp can be obtained via two globally defined variables:
  • tclpp_version returns the major and minor version number. For example 2.0 represents major release 2 and minor release 0.
  • tclpp_patchLevel returns the complete version number, including the patch level. For example 2.0b2 represents the second beta release of version 2.0.

    5.2 Class Information

The user can query information about classes at any time after it has been defined. These include the class names, the heritage, the methods and the variables. All information can be queried by one command: classinfo.

      5.2.1 Defined classes

Information about defined class names can be retrieved by the
	classinfo classes ?pattern?
command. If the pattern is not defined, it simply returns a list of all fully qualified class names, currently defined. the pattern can be used to restrict this output.

      5.2.2 Class heritage

Information about the heritage of a certain class can be retrieved by the
	classinfo heritage class
command. It will return the first line heritage of a class, not the entire tree. For example:
	class Base1 {}
	class Base2 : Base1 {}
	class Derived : Base2 {}
The heritage returned for the Derived class will only be the class Base2, not Base1 eventhough it is indirectly derived from that class as well.

      5.2.3 Class Variables

Information about the variables defined of a class can be retrieved by the
	classinfo variables class
command. It will return a list of variable information, each containing the variable name and type. Note, that the this variable is listed as well! For example:
	class Base {
	        variable scalar base_member
        }
	class Derived : Base {
		variable scalar derived_member
	}
The variable information of these two classes return the following lists:
	% classinfo variables Base
	{{this scalar} {base_member scalar}}
	% classinfo variables Derived
	{{this scalar} {derived_member scalar}}
In case of inheritance, it will not list the variables of all the base classes.

      5.2.4 Class Methods

Information about the methods defined of a class can be retrieved by the
	classinfo methods class
command. It will return a list of all the methods defined in the class. In case of inheritance it will not list the commands of the base classes.

    5.3 Run-Time Type Information

Apart from the global information about classes, one can also query information about a certain object. This is better known as RTTI (Run-Time Type Information) and is available through the objectinfo procedure.

      5.3.1 Class of an Object

Information about the type of an object (that is, its class name) can be retrieved by the
	objectinfo object isA 
command. It will return the fully qualified class name of the specified object. For example:
	class Base { .... }
	class Derived : Base { .... }
	Derived d

	objectinfo d isA	;# Returns '::Derived'

      5.3.2 Object is a class

Information whether an object's type equals (or one of its base classes equals) to a certain class can be retrieved by the
	objectinfo object isKindOf class 
command. It will return true if the object's type matches its class name or that of one of its base classes. For example
	class Base { .... }
	class Derived : Base { .... }
	Base b
	Derived d

	objectinfo b isKindOf Base	;# Returns true
	objectinfo b isKindOf Derived	;# Returns false
	objectinfo d isKindOf Derived	;# Returns true
	objectinfo d isKindOf Base	;# Returns true

6 Scoping

This chapter describes the rules for scoping and its behaviour in certain situations.

    6.1 Basic Scoping

Each class defines its own namespace, equal to the name of the class. Within this namespace, the member functions are defined as specified by the class definition.
Each instance of a class defines its own namespace as well, equal to the name of the instance. Within this namespace, the datamembers are defined as specified by that class (and its base classes).

    6.2 Using Namespaces

Classes and their instances are created within the namespace they are defined in. If no namespace is given, the global namespace (specified by '::') is used.
When a namespace is deleted, all its containing instances will be deleted properly. That is, it will destroy them and invoke the destructors.
Classes defined in the namespace are destroyed when the namespace is deleted. This will result in unpredictable behaviour if instances are still alive of these classes (either allocated directly, as a base class or as a data member of another class). The "rule of thumb" in this case is not to define classes in namespaces which might not have a life-time of the duration of the application.

7 Reference Manual

Not yet initiated.

8 Compatibility

This chapter discusses the differences with respect to backwards compatibility with older releases. This information is valuable for migrating code based on the old releases to this new release. 1