Worksheet Object-Oriented Programming

Table of Contents

1. Questions and Completion

This worksheet is not for credit, but it is recommended that you complete it as part of studying for the Final Exam.

If you have questions as you go through this worksheet, please feel free to post them on the discussion forum.

2. Inheritance

The Java programs in this section use implementation inheritance, abstract methods, dynamic dispatch, and super calls.

2.1. Example 1

Consider the following Java program. Work out what will be printed when it runs.

Verify your answer by compiling and running the program.

abstract class A {
  int f (int x) {
    System.out.format ("A.f (%d)%n", x);
    return (x == 0) ? g () : f (x - 1); 
  }

  abstract int g ();  

  void h () {
    System.out.println ("A.h ()"); 
  }
}


class B extends A {
  int g () { 
    System.out.println ("B.g ()"); 
    A r = this;
    r.h ();
    return 0;
  }

  void h () {
    System.out.println ("B.h ()"); 
    super.h ();
  }
}


public class Inheritance07 {
  public static void main (String[] args) {
    A x = new B (); 
    x.f (3);
  }
}

2.2. Example 2

Consider the following Java program. Work out what will be printed when it runs.

Verify your answer by compiling and running the program.

class A {
  int f (int x) {
    System.out.format ("A.f (%d)%n", x);
    return (x == 0) ? g () : f (x - 1); 
  }

  int g () {
    System.out.println ("A.g ()"); 
    return 0;
  }

  void h () {
    System.out.println ("A.h ()"); 
  }
}


class B extends A {
  int f (int x) {
    System.out.format ("B.f (%d)%n", x);
    return super.f (x);
  }

  int g () { 
    System.out.println ("B.g ()"); 
    A r = this;
    r.h ();
    return 0;
  }

  void h () {
    System.out.println ("B.h ()"); 
    super.h ();
  }
}


public class Inheritance08 {
  public static void main (String[] args) {
    A x = new B (); 
    x.f (3);
  }
}

2.3. Example 3

Consider the following Java program. Work out what will be printed when it runs.

Verify your answer by compiling and running the program (yes, it is meant to do that).

class A {
  int f (int x) {
    System.out.format ("A.f (%d)%n", x);
    return (x == 0) ? g () : f (x - 1); 
  }

  int g () {
    System.out.println ("A.g ()"); 
    return 0;
  }

  void h () {
    System.out.println ("A.h ()"); 
  }
}


class B extends A {
  int f (int x) {
    System.out.format ("B.f (%d)%n", x);
    return this.f (x);
  }

  int g () { 
    System.out.println ("B.g ()"); 
    A r = this;
    r.h ();
    return 0;
  }

  void h () {
    System.out.println ("B.h ()"); 
    super.h ();
  }
}


public class Inheritance09 {
  public static void main (String[] args) {
    A x = new B (); 
    x.f (3);
  }
}

3. Composition And Delegation

The Java programs in this section use composition and delegation.

3.1. Example 1

Consider the following Java program. Work out what will be printed when it runs.

Verify your answer by compiling and running the program.

class A {
  int f (int x) {
    System.out.format ("A.f (%d)%n", x);
    if (x == 0) {
      return g ();
    } else {
      return f (x - 1);
    }
  }

  int g () {
    System.out.println ("A.g ()");
    return 0;
  }
}

class B {
  A a = new A ();

  int f (int x) {
    System.out.format ("B.f (%d)%n", x);
    return a.f (x);
  }

  int g () {
    System.out.println ("B.g ()");
    return 0;
  }
}

public class Inheritance05 {
  public static void main (String[] args) {
    new B ().f (3);
  }
}

3.2. Example 2

Consider the following Java program. Work out what will be printed when it runs.

Verify your answer by compiling and running the program.

interface I {
  int f (int x);
  int g ();
}

class A implements I {
  I back = this;

  public int f (int x) {
    System.out.format ("A.f (%d)%n", x);
    if (x == 0) {
      return back.g ();
    } else {
      return back.f (x - 1);
    }
  }

  public int g () {
    System.out.println ("A.g ()");
    return 0;
  }
}

class B implements I {
  A a;

  B () {
    a = new A ();
    a.back = this;
  }

  public int f (int x) {
    System.out.format ("B.f (%d)%n", x);
    return a.f (x);
  }

  public int g () {
    System.out.println ("B.g ()");
    return 0;
  }
}

public class Inheritance06 {
  public static void main (String[] args) {
    new B ().f (3);
  }
}

4. Static And Dynamic Dispatch In C++

The C++ programs in this section contrast static/dynamic dispatch.

4.1. Example 1

Consider the following C++ program. Work out what will be printed when it runs.

Verify your answer by compiling and running the program.

#include <iostream>

class A { 
public: 
  void foo () { std::cout << "A" << std::endl; } 
};

class B : public A {
public: 
  void foo () { std::cout << "B" << std::endl; } 
};

int main () { 
  std::cout << "sizeof (A) = " << sizeof (A) << std::endl;
  std::cout << "sizeof (B) = " << sizeof (B) << std::endl;
  A* x = new B ();
  x->foo (); 
}

4.2. Example 2

Consider the following C++ program. Work out what will be printed when it runs.

Verify your answer by compiling and running the program.

#include <iostream>

class A { 
public: 
  virtual void foo () { std::cout << "A" << std::endl; } 
};

class B : public A {
public: 
  void foo () override { std::cout << "B" << std::endl; } 
};

int main () { 
  std::cout << "sizeof (A) = " << sizeof (A) << std::endl;
  std::cout << "sizeof (B) = " << sizeof (B) << std::endl;
  A* x = new B ();
  x->foo (); 
}

5. Look At Vtable Pointer In C++

Compile and run the following C++ program that uses subclassing and virtual functions (so vtable will be generated by the compiler).

#include <iostream>

class A { 
public: 
  int u;
  int v;
  virtual void f () { } 
  virtual void g () { } 
};

class B : public A {
public:  
  int w;
  int x;
  void g () override { } 
  virtual void h () { } 
};

int main () { 
  std::cout << "sizeof (A) = " << sizeof (A) << std::endl;
  std::cout << "sizeof (B) = " << sizeof (B) << std::endl;
  B* p1 = new B ();
  B* p2 = new B ();
  p1->u = 0x11111111;
  p1->v = 0x22222222;
  p1->w = 0x33333333;
  p1->x = 0x44444444;
  p2->u = 0x55555555;
  p2->v = 0x66666666;
  p2->w = 0x77777777;
  p2->x = 0x88888888;
  printf ("p1 = %p\n", (long *) (p1));
  printf ("p2 = %p\n", (long *) (p2));
  for (int i = 0; i < 5; i++) {
    printf ("p1[%d] = %p\n", i, ((long **) (p1))[i]);
    printf ("p2[%d] = %p\n", i, ((long **) (p2))[i]);
  }
  for (int i = 0; i < 3; i++) {
    printf ("p1[0][%d] = %p\n", i, ((long ***) (p1))[0][i]);
  }

  A* q1 = new A ();
  A* q2 = new A ();
  q1->u = 0x11111111;
  q1->v = 0x22222222;
  q2->u = 0x55555555;
  q2->v = 0x66666666;
  printf ("q1 = %p\n", (long *) (q1));
  printf ("q2 = %p\n", (long *) (q2));
  for (int i = 0; i < 5; i++) {
    printf ("q1[%d] = %p\n", i, ((long **) (q1))[i]);
    printf ("q2[%d] = %p\n", i, ((long **) (q2))[i]);
  }
  for (int i = 0; i < 3; i++) {
    printf ("q1[0][%d] = %p\n", i, ((long ***) (q1))[0][i]);
  }
}

You can compile this program using the GNU C++ compiler with the following command (assuming the file is stored in class07.cpp).

$ g++ -std=c++11 -o class07 class07.cpp 

Now interpret the output, i.e., explain which parts of the output are referring to fields, pointers to vtables, contents of vtables, etc.

The output from running the program on the instructor's system is as follows.

$ ./class07 
p1 = 0xc5f010
p2 = 0xc5f030
p1[0] = 0x400a10
p2[0] = 0x400a10
p1[1] = 0x2222222211111111
p2[1] = 0x6666666655555555
p1[2] = 0x4444444433333333
p2[2] = 0x8888888877777777
p1[0][0] = 0x4008d6
p1[0][1] = 0x4008ea
p1[0][2] = 0x4008f4

To help interpret this, the objdump command from the GNU binutils suite is useful (as used in the Systems courses). Note that C++ symbols are mangled by default, e.g., you see _ZN1A1fEv as the symbol name in the executable file, which really stands for A::f() (the f implementation from class A). The --demangle option for objdump does the demangling for you.

$ objdump --demangle -S class07 | grep :: | grep -v callq
00000000004008d6 <A::f()>:
00000000004008e0 <A::g()>:
00000000004008ea <B::g()>:
00000000004008f4 <B::h()>:
00000000004008fe <A::A()>:
0000000000400914 <B::B()>:

6. JS Prototype Usage

6.1. Set Prototype Property

Run the following JavaScript in your browser's console (Firefox and Chrome recommeded). In particular, convince yourself that the two counters have different values for n, but that they both have a next property.

function Counter () {
  this.n = 0;
}

Counter.prototype.next  = function () { 
  return this.n++; 
}

var c1 = new Counter ();
var c2 = new Counter ();

c1.next ();
c1.next ();
c2.next ();

6.2. Translate Java Class to JavaScript

Translate the following Java class to a JavaScript function (intended to be used with constructor context) with an appropriate prototype.

class Point {
  int x;
  int y;
  Point (int x, int y) {
    this.x = x;
    this.y = y;
  }
  void move (int x2, int y2) {
    x = x + x2;
    y = y + y2;
  }
}

6.3. Chained Prototype Property

Consider the following Java code that uses subclassing. The class B inherits (the implementation of) f from class A.

class A {
  A () {
  }

  void f () {
    System.out.println ("A.f ()");
  }
}

class B extends A {
  B () {
    super (); // Call to constructor of superclass A
  }

  void g () {
    System.out.println ("B.g ()");
  }
}

The following JavaScript code shows how to convert the implementation inheritance in Java above into JavaScript's prototype-based inheritance. (JavaScript is dynamically typed, so interface inheritance is not relevant in JavaScript).

The key is the use of Object.create. It is used to set the B.prototype property to a new object with a prototype of A.prototype. Consequently, if we lookup a property on an 'instance' of B, we search for a property of that name in the following order:

  1. In the 'instance' itself.
  2. In the B.prototype object.
  3. In the A.prototype object.

Try this out by running the following JavaScript in your browser's console (Firefox and Chrome recommeded).

function A () {
}

A.prototype.f  = function () { 
  console.log ("A.f ()");
}

function B () {
  A.call (this); // Call to constructor A
}

B.prototype = Object.create (A.prototype); // Creates a new object with =A.prototype= as its prototype

B.prototype.g  = function () { 
  console.log ("B.g ()");
}

var r = new A ();
var s = new B ();

r.f ();
r.g ();
s.f ();
s.g ();

function getProps (obj) {
  var props = [];
  for (var f in obj) { 
    props.push (f); 
  }
  return props;
}

function getProps2 (obj) {
  var props = [];
  for (var f in obj) { 
    if (obj.hasOwnProperty (f)) { props.push (f); }
  }
  return props;
}

getProps (r);  // Print out properties for r (including properties in the prototype chain)
getProps2 (r); // Print out properties for r (excluding the prototype chain)
getProps (s);  // Print out properties for s (including properties in the prototype chain)
getProps2 (s); // Print out properties for s (excluding the prototype chain)

7. Simulating OOP With Function Pointers In C

A number of large C programs/libraries emulate dynamic dispatch from object-oriented PLs using function pointers. In addition, there are well thought out frameworks, e.g., http://ldeniau.web.cern.ch/ldeniau/cos.html, for object-oriented programming in C. These approaches rely upon the programmer being very systematic, and the C compiler will not catch bugs that arise from not following the programming pattern correctly.

To give a flavor of these approaches and to make vtables more concrete, we now consider how to emulate simple object-oriented programming in C using function pointers. This approach is simpler than the ones mentioned above.

We will translate the following Java program to C.

interface Language {
  String greet (String name);
}

class English implements Language {
  public String greet (String name) { return "Hello " + name; }
}

class French implements Language {
  private int count = 0;
  public String greet (String name) { this.count++; return "Bonjour " + name; }
  public int getCount ()            { return this.count; }
}

public class SimpleOOP {
  public static void main (String[] args) {
    Language[] langs = new Language[] { new English (), new French (), };
    String name = System.console ().readLine ("What is your name? ");
    for (Language lang : langs) {
      System.out.println (lang.greet (name));
    }
    System.out.format ("French count = %d%n", ((French) langs[1]).getCount ());
  }
}

When run, the Java program reads a name from the console, and prints "hello" with the name in both English and French. The important part is that their is an interface with a greet method, and that interface is implemented in two different classes.

$ javac SimpleOOP.java 

$ java SimpleOOP
What is your name? alice
Hello alice
Bonjour alice
French count = 1

We can translate the Java program into the C program below with structures describing object instances and their vtables. Warning: for the sake of brevity, the array/string handling is unsafe in this program!

This C program is missing the code to actually invoke the greet and get_count methods. Read through the C program and fill in that code. Then compile and run your program.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct language_vtable;
struct language_inst;
struct english_vtable;
struct english_inst;
struct french_vtable;
struct french_inst;

/* ------------------------------------------------------------ */

struct language_vtable {
  char *(*greet) (struct language_inst *this, char *name);
};

struct language_inst {
  struct language_vtable *vtable;
};

/* ------------------------------------------------------------ */

struct english_vtable {
  char *(*greet) (struct english_inst *this, char *name);
};

struct english_inst {
  struct english_vtable *vtable;
};

char *english_greet (struct english_inst *this, char *name) {
  char *buffer = (char *) malloc (strlen (name));
  strcpy (buffer, "Hello ");
  strcat (buffer, name);
  return buffer;
};

struct english_vtable unique_english_vtable;

void init_unique_english_vtable () {
  unique_english_vtable.greet = &english_greet;
}

struct english_inst *new_english_inst () {
  struct english_inst *result = (struct english_inst *) malloc (sizeof (struct language_inst));
  result->vtable = &unique_english_vtable;
  return result;
}

/* ------------------------------------------------------------ */

struct french_vtable {
  char *(*greet) (struct french_inst *this, char *name);
  int (*get_count) (struct french_inst *this);
};

struct french_inst {
  struct french_vtable *vtable;
  int count;
};

char *french_greet (struct french_inst *this, char *name) {
  this->count++;
  char *buffer = (char *) malloc (strlen (name));
  strcpy (buffer, "Bonjour ");
  strcat (buffer, name);
  return buffer;
};

int french_get_count (struct french_inst *this) {
  return this->count;
};

struct french_vtable unique_french_vtable;

void init_unique_french_vtable () {
  unique_french_vtable.greet = &french_greet;
  unique_french_vtable.get_count = &french_get_count;
}

struct french_inst *new_french_inst () {
  struct french_inst *result = (struct french_inst *) malloc (sizeof (struct language_inst));
  result->vtable = &unique_french_vtable;
  return result;
}

/* ------------------------------------------------------------ */

void strip_new_line (char *buffer) {
  while (*buffer != 0 && *buffer != '\n') {
    buffer++;
  }
  if (*buffer == '\n') {
    *buffer = 0;
  }
}

/* ------------------------------------------------------------ */

int main (void) {
  init_unique_english_vtable ();
  init_unique_french_vtable ();

  struct language_inst *(langs[2]);
  langs[0] = (struct language_inst *) new_english_inst ();
  langs[1] = (struct language_inst *) new_french_inst ();

  printf ("What is your name? ");
  char name_buffer[1024];
  fgets (name_buffer, 1024, stdin);
  strip_new_line (name_buffer);
  for (int i = 0; i < 2; i++) {
    char *res = /* TODO: invoke the 'greet' method of langs[i] */;
    puts (res);
    free (res);
  }

  /* TODO: invoke the 'get_count' method of langs[1], then print the result */

  return 0;
}

Solution: Simulating OOP With Function Pointers In C

8. Solutions

8.1. Solution: Simulating OOP With Function Pointers In C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct language_vtable;
struct language_inst;
struct english_vtable;
struct english_inst;
struct french_vtable;
struct french_inst;

/* ------------------------------------------------------------ */

struct language_vtable {
  char *(*greet) (struct language_inst *this, char *name);
};

struct language_inst {
  struct language_vtable *vtable;
};

/* ------------------------------------------------------------ */

struct english_vtable {
  char *(*greet) (struct english_inst *this, char *name);
};

struct english_inst {
  struct english_vtable *vtable;
};

char *english_greet (struct english_inst *this, char *name) {
  char *buffer = (char *) malloc (strlen (name));
  strcpy (buffer, "Hello ");
  strcat (buffer, name);
  return buffer;
};

struct english_vtable unique_english_vtable;

void init_unique_english_vtable () {
  unique_english_vtable.greet = &english_greet;
}

struct english_inst *new_english_inst () {
  struct english_inst *result = (struct english_inst *) malloc (sizeof (struct language_inst));
  result->vtable = &unique_english_vtable;
  return result;
}

/* ------------------------------------------------------------ */

struct french_vtable {
  char *(*greet) (struct french_inst *this, char *name);
  int (*get_count) (struct french_inst *this);
};

struct french_inst {
  struct french_vtable *vtable;
  int count;
};

char *french_greet (struct french_inst *this, char *name) {
  this->count++;
  char *buffer = (char *) malloc (strlen (name));
  strcpy (buffer, "Bonjour ");
  strcat (buffer, name);
  return buffer;
};

int french_get_count (struct french_inst *this) {
  return this->count;
};

struct french_vtable unique_french_vtable;

void init_unique_french_vtable () {
  unique_french_vtable.greet = &french_greet;
  unique_french_vtable.get_count = &french_get_count;
}

struct french_inst *new_french_inst () {
  struct french_inst *result = (struct french_inst *) malloc (sizeof (struct language_inst));
  result->vtable = &unique_french_vtable;
  return result;
}

/* ------------------------------------------------------------ */

void strip_new_line (char *buffer) {
  while (*buffer != 0 && *buffer != '\n') {
    buffer++;
  }
  if (*buffer == '\n') {
    *buffer = 0;
  }
}

/* ------------------------------------------------------------ */

int main (void) {
  init_unique_english_vtable ();
  init_unique_french_vtable ();

  struct language_inst *(langs[2]);
  langs[0] = (struct language_inst *) new_english_inst ();
  langs[1] = (struct language_inst *) new_french_inst ();

  printf ("What is your name? ");
  char name_buffer[1024];
  fgets (name_buffer, 1024, stdin);
  strip_new_line (name_buffer);
  for (int i = 0; i < 2; i++) {
    char *res = (*(langs[i]->vtable->greet)) (langs[i], name_buffer);
    puts (res);
    free (res);
  }

  if (langs[1]->vtable != (struct language_vtable *) &unique_french_vtable) {
    fprintf (stderr, "Cannot safely cast to french_inst\n");
    exit (1);
  }
  struct french_inst *french = (struct french_inst *) langs[1];
  int count = (*(french->vtable->get_count)) (french);
  printf ("French count = %d\n", count);

  return 0;
}
$ gcc -std=c99 -o simple-oop-solution simple-oop-solution.c

$ ./simple-oop-solution 
What is your name? alice
Hello alice
Bonjour alice
French count = 1

Author: James Riely

Created: 2024-04-17 Wed 17:43

Validate