Programming Languages

 

G22.2110 Summer 1998

 

 

 

Class #8

 

OO Imperative Programming Languages - Part II

 

 

No homework.

Read Sethi’s Chapters 7, 15.3, 15.4, handouts and references

 

Contents

 

C8.0. Assignments and PL Project status

C8.1. The Smalltalk Programming Language

C8.2. Support for Object-Oriented Programming in C++

C8.3. Support for Object-Oriented Programming in Ada95

C8.4. Comments on Sethi's Chapters 7, 15.3, 15.4

 

C8.0. Assignments and PL Project status

 

C8.1. The Smalltalk Programming Language

 

See Notes from class #6/7

 

C8.2. Support for Object-Oriented Programming in C++

 

Evolution of C++ from C and Simula 67

 

C with classes:

 

Classes and inheritance without performance penalty relative to C.

Addition of function parameter type checking and conversion.

Addition of classes related to those of Simula 67 and Smalltalk.

Addition of derived classes, public/private access control of inherited components constructor and destructor functions, and friend classes.

1981: inline functions, default parameters, and overloading of the assignment operator.

 

1984: C++

Inclusion of virtual functions (provide dynamic binding of function calls to specific function definitions.

Inclusion of function names, operator overloading, and reference types.

 

1985:

First available implementation: Cfront (translates C++ programs into C programs)

 

June 1989: C++ Release 2.0

Support for multiple inheritance.

Abstract classes.

Type-safe linkage (compiler checks the consistency of function types across separate compilation units).

 

1990: C++ Release 3.0

Templates (provide parameterized types).

Exception handling.

 

Abstract data types in C++

 

Classes in C++ are an addition to the normal typing system in the language.

 

#include <iostream.h> // provides visibility to a standard input and output package (use by cout)

 

class stack {

friend function_or_class_name // grant nonmember functions or classes access to private mbrs

private: // These members are visible only to friends and other members

int *stack_ptr;

int max_len;

int top_ptr;

public: // These members are the interface to the class

stack( ) { stack_ptr = new int [100]; // A constructor

max_len = 99;

top_ptr = -1;}

~stack( ) { delete [ ] stack_ptr;}; // A destructor

void push (int number) {

if (top_ptr == max_len)

cout << "Error in push-stack is full\n";

else stack_ptr[++top_ptr] = number;

}

void pop ( ) {

if (top_ptr == -1)

cout << "Error in pop-stack is empty\n"

else top_ptr--;

}

int top ( ) { return (stack_ptr[top_ptr]);}

int empty ( ) { return (top_ptr == -1);}

}

 

void main ( ) {

int top_one;

stack stk; // Creates an instance of the stack class, it is a stack dynamic class

// (class instances can also be static, and heap dynamic)

stk.pu sh(42);

stk.push(17);

stk.pop();

top_one = stk.top( );

…

}

 

Inheritance

 

A C++ class can be derived from an existing class which is then its "parent" or "base" class.

Unlike Smalltalk, a C++ class can also be stand-alone, whitout a superclass.

The data defined in a class definition are called data members of that class.

The functions defined in a class definition are called member functions of that class.

Some or all the data members and member functions of the base class may be inherited by the derived class, which can also add new data members and member functions, and modify inherited members.

Accessibility of the members of subclasses can be different than that of the corresponding members of the base class (derived classes can therefore be prevented from being subtypes).

 

Private members are accessible only by member functions and friends (functions or classes) of the class.

Public members are accessible by any functions.

Protected members are like private members, except in derived classes:

 

Syntatic form of a derived class:

class derived_class_name: access_mode base_class_name

{ data member and member function declarations};

access_mode can be either public or private.

In a public derived class, public and protected members of a base class are also public and protected.

In a private derived class, both the public and protected members of the base class are private (when placed in a class hierarchy, it cuts off access to all members of all ancestor classes to all successor classes, and protected members may or may not be accessible to subsequent classes).

 

e.g.,

class base_class {

private:

int a;

float x;

protected:

int b;

float y;

public:

int c;

float z;

};

 

class subclass_1 : public base_class { … };

class subclass_2 : private base_class { … };

 

subclass_1:

b and y are protected

c and z are public

 

subclass_2:

b, y, c, and z are private

No derived class of subclass_2 can have members with access to any member of base_class

 

a and x are not accessible in either subclass_1 or subclass_2

 

class subclass_3 : private base_class {

base_class :: c;

…

}

Instances of subclass_3 can now access c as it has been reexported in the derived class.

 

e.g.,

class single_linked_list {

class node {

friend class single_linked_list;

// enclosing classes have no special access rights to members of their nested classes

private:

node *link;

int contents;

};

private:

node *head;

public:

single_linked_list ( ) {head = 0};

void insert_at_head (int);

void insert_at_tail (int);

int remove_at_head ( );

int empty ( );

};

 

class stack : public single_linked_list {

public:

stack ( ) { }

void push (int value) { single_linked_list :: insert_at_head (int value);}

int pop ( ) { return single_linked_list::remove_at_head ( );}

};

 

class queue : public single_linked_list {

public:

queue ( ) { }

void enqueue (int value) { single_linked_list :: insert_at_tail (int value);}

int dequeue ( ) { single_linked_list::remove_at_head ( );}

};

 

Objects of both the stack and queue subclasses can access the empty function of the base class.

Note that both subclasses define constructor functions that do nothing.

Problem is that a stack object could access insert_at_tail thereby destroying the integrity of its stack, similarly a queue object could access insert_at_head( ). The following definitions avoid this:

 

class stack_2 : private single_linked_list {

public:

stack_2 ( ) { }

void push (int value) {single_linked_list :: insert_at_head (int value); }

int pop ( ) { return single_linked_list::remove_at_head ( );}

single_linked_list::empty;

};

class queue_2 : private single_linked_list {

public:

queue_2 ( ) { }

void enqueue (int value) { single_linked_list::insert_at_tail (int value);}

int dequeue ( ) { single_linked_list::remove_at_head ( );}

single_linked_list::empty;

};

 

The above illustrates the fact that derived types may not be subtypes.

 

Example of friends:

When a subprogram must be written that must access the members of two different classes.

e.g., a program uses a class for vectors and one for matrices and a subprogram is needed to multiply objects of both classes. In this case the multiply function is simply made a friend of both classes.

 

Multiple inheritance:

class A { … };

class B { … };

class C : public A, public B { … };

If both A and B happen to include members with the same name, they can be unambiguously referenced in objects of class C by using the scope resolution operator.

 

Dynamic Binding

 

Member functions that must be dynamically bound must be declared to be virtual functions by preceding their headers with the reserved word virtual which can only appear in the class body.

A pointer or reference variable that has the type of a base class can be used to point to objects of any class derived from that base class (opposite is not true).

If such a base class pointer or reference variable is used to call a function that is defined in several of the derived classes, the call must be dynamically bound to the correct function definition.

 

e.g.,

class shape { // Abstract class (includes a pure virtual function), no object can be created

public:

virtual void draw ( ) = 0; // pure virtual function syntax (no body/cannot be called)

// function must be redefined in derived classes.

…

}

class circle : public shape {

public:

virtual void draw ( ) { … }

…

}

class rectangle : public shape {

public:

virtual void draw ( ) { … }

…

}

class square : public shape {

public:

virtual void draw ( ) { … }

…

}

 

square s;

rectangle r;

shape &ref_shape = s; // a reference to the square s

ref_shape.draw ( ); // dynamically bound to draw in square

r.draw ( ); // statically bond to draw in rectangle

 

Implementation of dynamic binding of calls to virtual member functions:

At the time an object is created, a list of pointers to virtual functions is created which is then used in the dynamic binding of function calls to functions.

Calls to virtual functions are handled by indirect addressing and cost slightly more than calls that are statically bound (still cheaper than Smalltalk that requires a method search).

 

Exception Handling

 

Exception Handlers:

 

try { // code that is expected to raise an exception

}

catch ( formal parameter ) {

// a handler body

}

…

catch ( formal parameter ) {

// a handler body

}

 

Exceptions enacted only by explicit user code statements.

 

Binding Exceptions to Handlers:

 

throw[expression]

 

Continuation:

 

After a handler completes its execution, control flows to the first statement following the last handler in the sequence of handlers of which it is an element.

A handler may reraise an exception, using a throw without an expression, in which case that exception is propagated to the caller.

Summary

 

Inheritance more intricate than that of Smalltalk: access controls within the class definition, derivation access controls, friend functions and classes, multiple inheritance.

 

Static and faster dynamic binding than Smalltalk: fixed cost regardless of how distant in the class hierarchy the definition appears. Five more memory references for virtual functions rather than for statically bound calls.

 

Static type checking: generic classes via template facility (retains the benefit of static type checking). In Smalltalk, all type checking is dynamic.

 

Greater efficiency than Smalltalk.

 

8.3. Support for Object-Oriented Programming in Ada95

 

Ada 95 was derived from Ada 83 with some significant extensions such as concurrency support.

Ada 83 already included constructs for building ADTs, so features for supporting inheritance and dynamic binding were added in Ada 95 (goal was to require minimal changes to the type and package structures of Ada 83, and retaining as much static type checking as possible).

 

Classes and Objects

 

Ada 95 classes are a new category of types called "tagged types" which can be either records or private types.

They are encapsulated in packages, which allow them to be separately compiled.

Each object of a tagged type implicitly includes a system-maintained tag that indicates its type.

The subprograms that define the operations on a tagged type appear in the same declaration list as the type declaration and must have at least one parameter (or the return type) that is of the tagged type.

 

e.g.,

package PERSON_PKG is

type PERSON is tagged private;

procedure DISPLAY (P: in out PERSON);

private

type PERSON is tagged

record

NAME : STRING (1 .. 30);

ADDRESS : STRING (1 .. 30);

AGE : INTEGER;

end record;

end PERSON_PKG

 

No implicit calling of constructors or destructors.

 

Inheritance

 

Ada 83 has a restricted form of inheritance in its derived types and subtypes (a new type can be defined on the basis of an existing type, but the only modification allowed is to restrict the range of values of the new type).

 

Derived classes in Ada 95 are based on tagged types. New entities are added to the inherited entities by including a record definition.

 

e.g.,

with PERSON_PKG; use PERSON_PKG;

package STUDENT_PKG is

type STUDENT is new PERSON with

record

GRADE_POINT_AVERAGE : FLOAT;

GRADE_LEVEL : INTEGER;

end record;

procedure DISPLAY (ST : in STUDENT);

end STUDENT_PKG;

 

Child library packages can be used in place of the friend definitions in C++.

 

Multiple inheritance effects can be achieved using generics (not as elegant as the C++ method).

 

Dynamic Binding

 

Ada 95 provides both static and dynamic binding of function calls to function definitions in tagged types.

Dynamic binding is forced by using classwide types (types that represent all of the types in a class hierarchy).

For a tagged type T, the classwide type is specified with T' class.

 

e.g., procedure that can call either of the two DISPLAY procedures defined in PERSON and STUDENT

 

procedure DISPLAY_ANY_PERSON (P: in out PERSON'class) is

begin

DISPLAY (P);

end DISPLAY_ANY_PERSON;

 

with PERSON_PKG; use PERSON_PKG;

with STUDENT_PKG; use STUDENT_PKG;

P : PERSON;

S : STUDENT;

DISPLAY_ANY_PERSON (P); -- call the DISPLAY in PERSON

DISPLAY_ANY_PERSON (S); -- call the DISPLAY in STUDENT

 

Purely abstract base types can be defined in Ada 95 by including the reserved word "abstract" in the type definitions and the subprogram definitions.

Furthermore, the subprogram definitions cannot have bodies.

 

e.g.,

package BASE_PKG is

type T is abstract tagged null record;

procedure DO_IT (A : T) is abstract;

end BASE_PKG;

 

Evaluation

 

The Ada 95 class mechanism is built into the existing type system (not its own type system as for C++ classes).

No oddities in Ada 95 such as some that are possible in C++:

e.g., class B_CLASS derived from A_CLASS, and B is a pointer to B_CLASS objects, and A is a pointer to A_CLASS objects, then

B = A; // An illegal assignment

A = B; // A legal assignment

 

C++ offers a better form of multiple inheritance than Ada 95.

 

The use of child library units to control access to the entities of the parent class in Ada 95 seems to be a cleaner solution than the friend functions and classes of C++ (if the need for a friend is not known when a class is defined, it will need to be changed and recompiled later).

 

Ada 95 does not include useful constructors and destructors for initialization of objects and the handling of heap allocation and deallocation.

 

In Ada 95, each call to a function can specify whether it will be statically or dynamically bound, regardless of the design of the root class as in C++.

 

Dynamic binding in C++ is restricted to pointers and references to objects, rather than the objects themselves. Ada 95 has no such restrictions.

 

Exception handling

 

Exception handlers:

 

when exception_choice { | exception_choice} =>

statement_sequence

 

exception_choice has the form

exception_name | others

 

Handlers are local to the code in which the exception can be raised.

Handlers can be included in blocks or in the bodies of subprograms, packages, or tasks.

 

e.g.,

begin

-- the block or unit body --

exception

when exception_name_1 =>

-- first handler --

when exception_name_2 =>

-- second handler --

-- other handlers --

end;

 

Binding Exceptions to Handlers:

 

Exception raised in procedure is propagated implicitly to the calling program unit at the point of the call if the procedure has no handler for it, then to that unit's caller and so on possibly up to the main program in which case the program is terminated.

 

Exceptions raised in a block are propagated to the next larger enclosing scope (code that called it).

 

Exception raised in a package body are propagated to the declaration section of the unit containing the package declaration. If the package happens to be a library unit, the program is terminated.

 

If a task does not have a handler for an exception, it is marked as completed and the exception is not propagated.

 

Exceptions can also occur during the elaboration of the declarative sections of subprograms, blocks, packages, and tasks.

 

Continuation:

 

Control never returns implicitly to the raising block or unit after the exception is handled.

In the case of a block, a statement can be retried after it raises an exception and that exception is handled.

 

Other Design Choices:

 

Ada includes five built-in exceptions, which are usually categories of exceptions.

 

e.g., CONSTRAINT_ERROR (raised when an array subscript is out of range, when there is a range error in a numeric variable with a range restriction, when a reference is made to a record field that is not present in a discriminated union, and in a few additional situations).

 

User-defined exceptions are defined with the following declaration:

 

exception_name_list:exception

 

These exceptions must be raised explicitly with the raise statement which has the general form:

raise [exception_name]

 

Syntax for suppressible checks:

pragma SUPPRESS (INDEX_CHECK);

 

C8.4. Comments on Sethi's Chapters 7, 15.3, 15.4