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.
1.1
Context of Tcl
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.
1.2
Structure of this manual
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.
2.1
Procedural versus Object-Oriented
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.
2.2
Classes and Objects
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 Shapebase 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:
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:
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:
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.