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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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'
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.
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).
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.