This note was taken after studying Concepts of Programming Languages by Robert W. Sebesta, 10th edition.

Object-oriented Programming

A language that is object-oriented must provide support for 3 key language features:

  1. Abstact data types (classes)

  2. Inheritance

  3. Dynamic binding of method calls to methods

Inheritance

Problems with abtract data types:

  • Some features and capabilities of the existing types are not always adequate for use. The old type requires at least some minor modifications.
  • Given the first problem, if we create a new abtract data type that is slightly adjusted, the type definitions are all independent and are at the same level: Meaning, we are not able to distinguish the relation in between the abstract data types (classes).

If a new abstract data type can inherit the data and functionality of some existing type, and is also allowed to modify some of those entities and add new entities, reuse is greatly facilitated without requiring changes to the reused abstract data type. Inheritance allows for the definition of hierarchies of related classes.

If class B is inherited from class A, B is called derived class or subclass and A is caleld parent class or superclass.

The derived class can differ from the parent in the following ways:

  1. The parent calss can define some of its variables or methods to have private access, which does not become accessible by the subclass.
  2. The subclass can add variables and methods to those inherited from the parent class.
  3. The subclass can modify the behavior of one of more of its inherited methods. (override)

Classes have two kinds of methods and two kinds of variables.

  • Instance methods
  • Instance variables
  • Class variables- belong to the class rather than the object.
  • Class methods- can perform operations on the class, and possibly on the objects of the class.

One disadvantage of inheritance as a means of increasing the possibility of reuse is that it creates dependencies among the classes in an inheritance hierarchy. This result works against one of the advantages of abstract data types, which is that they are independent of each other.

Dynamic Binding

Dynamic binding is the process of determining at runtime which method or function to invoke when you make a method call on an object. This is a kind of polymorphism provided by the dynamic binding of messages to method definitions, sometimes called dynamic dispatch.

Polymorphism is statically typed, however, when there are method calls that are dynamically binded, it is dynamic since you may not know the specific type of the object that a variable is referencing at compile time.

One purpose of dynamic binding is to allow software systems to be more easily extended during both development and maintenance.

Example:

The is a base class Car and has subclasses for each car model. Each subclass contrains information about a specific car model. When a user browses the car models, the car models are stored in an array of Car.

Dynamic Binding comes into play when the system needs to print information about a car inside the array. Since the array contains references to objects of different subclasses (representing different car models), dynamic binding ensures that the correct implementation of the method is called at runtime. This allows the system to print the relevant information for each car model without needing to know their specific types at compile time.

Extensibility comes into play when new car models are added over time. With dynamic binding in place, adding new subclasses (representing new car models) does not require making extensive changes to the system. This makes the software more easily extendable and maintainable, as new cars can be seamlessly integrated into the existing system without affecting its core functionality.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class Car {
String make;
String model;

Car(String make, String model) {
this.make = make;
this.model = model;
}

void printInfo() {
System.out.println("Make: " + make);
System.out.println("Model: " + model);
}
}

class Sedan extends Car {
int numberOfDoors;

Sedan(String make, String model, int numberOfDoors) {
super(make, model);
this.numberOfDoors = numberOfDoors;
}

@Override
void printInfo() {
super.printInfo();
System.out.println("Type: Sedan");
System.out.println("Number of Doors: " + numberOfDoors);
}
}

class SUV extends Car {
boolean fourWheelDrive;

SUV(String make, String model, boolean fourWheelDrive) {
super(make, model);
this.fourWheelDrive = fourWheelDrive;
}

@Override
void printInfo() {
super.printInfo();
System.out.println("Type: SUV");
System.out.println("Four-Wheel Drive: " + fourWheelDrive);
}
}

public class CarCatalog {
public static void main(String[] args) {
// Create instances of cars
Car car1 = new Sedan("Toyota", "Camry", 4);
Car car2 = new SUV("Ford", "Explorer", true);

// Create an array to store user's interested cars
Car[] carsOfInterest = new Car[2];
carsOfInterest[0] = car1;
carsOfInterest[1] = car2;

// Print information about the cars of interest using dynamic binding
System.out.println("Cars of Interest:");
for (Car car : carsOfInterest) {
car.printInfo();
System.out.println("------------");
}
}
}

abstract method and abstract class are declared but not defined in the parent class so that the child class needs to provide the implementations.

Design Issues for OOP

Exclusivity of Objects

The primary disadvantage of Objects is that simple operations must be done through the message-passing process, which often makes them slower than similar operations in an imperative model, where single machine instructions implement such simple operations.

  1. Object-oriented programming retains the complete collection of types from a traditional imperative programming language and simply add the object typing model.
  2. Using objects is to have an imperative-style type structure for the primitive scalar types, but implement all structured types (wrapper class) as objects. Bytes, Integer, Char, …

Are Subclasses Subtypes?

Does an “is-a” relationship hold between a derived class and its parent class?

An essential aspect of the “is-a” relationship is behavioral equivalence. Objects of the derived class should behave in a manner consistent with the parent class, allowing a variable of the derived class type to be used wherever a variable of the parent class type is legal.

There can be various ways in which a subclass can differ from its base class. These differences can include additional methods, fewer methods, parameter type differences, return type differences, or method body differences. However, many programming languages impose restrictions to ensure that a subclass remains a subtype of its parent class.

Multiple Inheritance

Subclass C and inherit from class A and class B. This might be troublesome if class A and B both share a variable or method name.

The use of multiple inheritance can easily lead to complex program organizations. Many who have attempted to use multiple inheritance have found that designing the classes to be used as multiple parents is difficult.

Allocation and Deallocation of Objects

  1. Allocation of Objects:
    • One design consideration is where objects should be allocated from in a programming language. Objects can be allocated from various places, such as the runtime stack or explicitly on the heap using operators like new.
    • If objects behave like abstract data types and can be allocated from anywhere, it provides flexibility. When all objects are heap-dynamic, it simplifies object creation and access because they are always accessed through pointers or reference variables. This uniformity simplifies assignment operations since they become pointer or reference value changes.
    • When objects are stack-dynamic, there can be issues related to subtypes. For example, if one class (B) is a child of another class (A) and B is considered a subtype of A, an object of type B can be assigned to a variable of type A. This assignment works smoothly when objects are heap-dynamic (only pointers are assigned). However, when objects are stack-dynamic, value variables need to be copied to the target object’s space. This can lead to problems if B adds additional data fields beyond what it inherits from A. This situation is referred to as “object slicing.”
  2. Deallocation of Objects:
    • The second design question is about how objects are deallocated, particularly when they are allocated from the heap.
    • Implicit deallocation means that some mechanism for automatic storage reclamation is required, often referred to as garbage collection.
    • Explicit deallocation means that developers are responsible for explicitly releasing the memory used by objects when they are no longer needed. This approach raises concerns about potential issues like dangling pointers or references, where a pointer or reference still points to an object that has been deallocated or destroyed.

Dynamic and Static Binding

  1. Dynamic Binding: Dynamic binding is a fundamental concept in object-oriented programming (OOP). It refers to the process of determining at runtime which method or function to invoke based on the actual type of the object that the method is called on. Dynamic binding allows for flexibility and polymorphism in OOP, as different objects can respond to the same method call in a way that’s appropriate for their specific types.
  2. Static Binding: Static binding, on the other hand, is the opposite of dynamic binding. In static binding, the method to be called is determined at compile time based on the declared type of the reference variable, not at runtime based on the actual type of the object. Static binding is faster because the binding is resolved at compile time, and there is no need for runtime type checks.

Static bindings are faster. So, if a binding need not be dynamic, why pay the price?

Nested Classes

One of the primary reasons for nesting class definitions is information hiding. If a new class is needed and is intended to be used exclusively by one specific class, there is no need to define it in a way that makes it accessible to other classes in the broader scope. In such cases, the new class can be nested inside the class that needs it. This nesting allows for a tighter scope and better encapsulation.

Initialization of Objects

The initialization issue is whether and how objects are initialized to values when they are created. This is more complicated than may be first thought. The first question is whether objects must be initialized manually or through some implicit mechanism. When an object of a subclass is created, is the associated initialization of the inherited parent class member implicit or must the programmer explicitly deal with it.

Support for Object-Oriented Programming in C++

The objects of C++ can be static, stack dynamic, or heap dynamic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

// Static object with file scope
int staticVar = 42;

int main() {
// Stack dynamic objects
int stackVar = 10;

// Heap dynamic objects
int* heapVar = new int(30);

// Accessing the static variable
std::cout << "Static Variable: " << staticVar << std::endl;
std::cout << "Stack Variable 1: " << stackVar << std::endl;
std::cout << "Heap Variable 1: " << *heapVar << std::endl;

// Deallocate heap dynamic objects to avoid memory leaks
delete heapVar1;
return 0;
}

Inheritance

Class members can be private, protected, or public. Private members are accessible only by member functions and friends of the class. Both functions and classes can be declared to be friends of a class and thereby be given access to its private members. Public members are visible everywhere. Protected members are like private members, except in derived classes, whose access is described next. Derived classes can modify accessibility for their inherited members. The syntactic form of a derived class is

1
class derived_class_name:derivation_mode base_class_name {data member and member function declarations};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 {...};

In subclass_1, b and y are protected, and c and z are public. In 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. The data members a and x in base_class are not accessible in either subclass_1 or subclass_2.

Dynamic Binding

Member functions in C++ can be statically bound or dynamically bound. Statically bound functions have their calls resolved at compile time, while dynamically bound functions have their calls resolved at runtime. Dynamic binding is particularly useful for polymorphic behavior.

To enable dynamic binding, member functions must be declared as virtual functions using the virtual keyword in the class definition. Virtual functions have a base class definition but can be overridden in derived classes. Pure virtual functions have no implementation and must be overridden in derived classes.

Implementation of Object-Oriented Constructs

Instance Data Storage

In C++, classes are defined as extensions of C’s record structures—structs. This similarity suggests a storage structure for the instance variables of class instances—that of a record. This form of this structure is called a class instance record (CIR). The structure of a CIR is static, so it is built at compile time and used as a template for the creation of the data of class instances. Every class has its own CIR. When a derivation takes place, the CIR for the subclass is a copy of that of the parent class, with entries for the new instance variables added at the end.
Because the structure of the CIR is static, access to all instance variables can be done as it is in records, using constant offsets from the beginning of the CIR instance. This makes these accesses as efficient as those for the fields of records.

Dynamic Binding of Method Calls to Methods

Virtual Method Table (vtable): To manage dynamically bound methods efficiently, a vtable is used. A vtable is a storage structure that contains pointers to all the dynamically bound methods for a class. Each class that has dynamically bound methods has its own vtable.