Data Abstraction and Object Orientation

Prev: Subroutines and Control Abstraction Next: Functional Languages

Prev: Subroutines and Control Abstraction Next: Functional Languages

10.1 Check Your Understanding

  1. What are generally considered to be the three defining characteristics of object-oriented programming?
  2. In what programming language of the 1960s does object orientation find its roots? Who invented that language? Summarize the evolution of the three defining characteristics since that time.
  3. Name three important benefits of abstraction.
  4. What are the more common names for subroutine member and data member?
  5. What is a property in C#?
  6. What is the purpose of the private part of an object interface? Why can’t it be hidden completely?
  7. What is the purpose of the :: operator in C++?
  8. Explain why in-line subroutines are particularly important in object-oriented languages.
  9. What are constructors and destructors?
  10. Give two other terms, each, for base class and derived class.
  11. Explain why generics may be useful in an object-oriented language, despite the extensive polymorphism already provided by inheritance.

10.2 Check Your Understanding

  1. What is meant by an opaque export from a module?
  2. What are private types in Ada?
  3. Explain the significance of the this parameter in object-oriented languages.
  4. How do Java and C# make do without explicit class headers?
  5. Explain the distinctions among private, protected, and public class members in C++.
  6. Explain the distinctions among private, protected, and public base classes in C++.
  7. Describe the notion of selective availability in Eiffel.
  8. How do the rules for member name visibility in Smalltalk and Objective-C differ from the rules of most other object-oriented languages?
  9. How do inner classes in Java differ from most other nested classes?
  10. Describe the key design difference between the object-oriented features of Smalltalk, Eiffel, and C++ on the one hand, and Ada, CLOS, and Fortran on the other.
  11. What are extension methods in C#? What purpose do they serve?

10.3 Check Your Understanding

  1. Does a constructor allocate space for an object? Explain.
  2. What is a metaclass in Smalltalk?
  3. Why is object initialization simpler in a language with a reference model of variables, as opposed to a value model?
  4. How does a C++ compiler, or Java or C# compiler, tell which constructor to use for a given object? How does the answer differ for Eiffel and Smalltalk?
  5. What is escape analysis? Describe why it might be useful in a language with a reference model of variables.
  6. Summarize the rules in C++ that determine the order in which constructors are called for a class, its base class or classes, and the classes of its fields. How are these rules simplified in other languages?
  7. Explain the difference between initialization and assignment in C++.
  8. Why does C++ need destructors more than Eiffel does?

10.4 Check Your Understanding

  1. Explain the difference between dynamic and static method binding, that is, between virtual and nonvirtual methods.
  2. Summarize the fundamental argument for dynamic method binding. Why do C++ and C# use static method binding by default?
  3. Explain the distinction between redefining and overriding a method.
  4. What is a class-wide type in Ada 95?
  5. Explain the connection between dynamic method binding and polymorphism.
  6. What is an abstract method, also called a pure virtual method in C++ and a deferred feature in Eiffel?
  7. What is reverse assignment? Why does it require a run-time check?
  8. What is a vtable? How is it used?
  9. What is the fragile base class problem?
  10. What is an abstract deferred class?
  11. Explain the importance of virtual methods for object closures.

10.5-10.7 Check Your Understanding

  1. What is mix-in inheritance? What problem does it solve?
  2. Outline a possible implementation of mix-in inheritance for a language with statically typed objects. Explain in particular the need for interface-specific views of an object.
  3. Describe how mix-ins, and their implementation, can be extended with default method implementations, static constant fields, and even mutable fields.
  4. What does true multiple inheritance make possible that mix-in inheritance does not?
  5. What is repeated inheritance? What is the distinction between replicated and shared repeated inheritance?
  6. What does it mean for a language to provide a uniform object model? Name two languages that do so.

10.9 Exercises

  1. Some language designers argue that object orientation eliminates the need for nested subroutines. Do you agree? Why or why not?
  2. Design a class hierarchy to represent syntax trees for the CFG of Figure 4.5. Provide a method in each class to return the value of a node. Provide constructors that play the role of the make_leaf, make_un_op, and make_bin_op subroutines.
  3. Repeat the previous exercise, but using a variant record union type to represent syntax tree nodes. Repeat again using type extensions. Compare the three solutions in terms of clarity, abstraction, type safety, and extensibility.
  4. Using the C# indexer mechanism, create a hash table class that can be indexed like an array. In effect, create a simple version of the System.Collections.Hashtable container class. Alternatively, use an overloaded operator[] to build a similar class in C++.
  5. In the spirit of Example 10.8, write a double-ended queue deque abstraction, pronounced deck, derived from a doubly linked list base class.
  6. Use templates or generics to abstract your solutions to the previous two questions over the type of data in the container.
  7. Repeat Exercise 10.5 in Python or Ruby. Write a simple program to demonstrate that generics are not needed to abstract over types. What happens if you mix objects of different types in the same deque?
  8. When using the list class of Example 10.17, the typical C++ programmer will use a pointer type for generic parameter V, so that list_nodes point to the elements of the list. An alternative implementation would include next and prev pointers for the list within the elements themselves, typically by arranging for the element type to inherit from something like the gp_list_node class of Example 10.14. The result is sometimes called an intrusive list.
    • Explain how you might build intrusive lists in C++ without requiring users to pepper their code with explicit type casts. Hint: given multiple inheritance, you will probably need to determine, for each concrete element type, the offset within the representation of the type at which the next and prev pointers appear. For further ideas, search for information on the boost::intrusive::list class of the popular Boost library.
    • Discuss the relative advantages and disadvantages of intrusive and non-intrusive lists.
  9. Can you emulate the inner class of Example 10.22 in C# or C++? Hint: you will need an explicit version of Java’s hidden reference to the surrounding class.
  10. Write a package body for the list abstraction of Figure 10.2.
  11. Rewrite the list and queue abstractions in Eiffel, Java, and/or C#.
  12. Using C++, Java, or C#, implement a Complex class in the spirit of Example 10.25. Discuss the time and space tradeoffs between maintaining all four values x, y, rho, and theta in the state of the object, or keeping only two and computing the others on demand.
  13. Repeat the previous two exercises for Python and/or Ruby.
  14. Compare Java final methods with C++ nonvirtual methods. How are they the same? How are they different?
  15. In several object-oriented languages, including C++ and Eiffel, a derived class can hide members of the base class. In C++, for example, we can declare a base class to be public, protected, or private:
class B : public A { ...
    // public members of A are public members of B
    // protected members of A are protected members of B
...
class C : protected A { ...
    // public and protected members of A are protected members of C
...
class D : private A { ...
    // public and protected members of A are private members of D

In all cases, private members of A are inaccessible to methods of B, C, or D. Consider the impact of protected and private base classes on dynamic method binding. Under what circumstances can a reference to an object of class B, C, or D be assigned into a variable of type A*? 16. What happens to the implementation of a class if we redefine a data member? For example, suppose we have:

class foo {
public:
    int a;
    char *b;
};
...
class bar : public foo {
public:
    float c;
    int b;
};

Does the representation of a bar object contain one b field or two? If two, are both accessible, or only one? Under what circumstances? 17. Discuss the relative merits of classes and type extensions. Which do you prefer? Why? 18. Building on the outline of Example 10.28, write a program that illustrates the difference between copy constructors and operator= in C++. Your code should include examples of each situation in which one of these may be called; do not forget parameter passing and function returns. Instrument the copy constructors and assignment operators in each of your classes so that they will print their names when called. Run your program to verify that its behavior matches your expectations. 19. What do you think of the decision, in C++, C#, and Ada 95, to use static method binding rather than dynamic binding by default? Is the gain in implementation speed worth the loss in abstraction and reusability? Assuming that we sometimes want static binding, do you prefer the method-by-method approach of C++ and C#, or the variable-by-variable approach of Ada 95? Why? 20. If foo is an abstract class in a C++ program, why is it acceptable to declare variables of type foo*, but not of type foo? 21. Consider the Java program shown in Figure 10.8. Assume that this is to be compiled to native code on a machine with 4-byte addresses.

  • Draw a picture of the layout in memory of the object created at line 15. Show all virtual function tables.
  • Give assembly-level pseudocode for the call to c.val at line 19. You may assume that the address of c is in register r1 immediately before the call, and that this same register should be used to pass the hidden this parameter. You may ignore the need to save and restore registers, and do not worry about where to put the return value.
  • Give assembly-level pseudocode for the call to c.ping at line 17. Again, assume that the address of c is in register r1, that this is the same register that should be used to pass this, and that you do not need to save or restore any registers.
  • Give assembly-level pseudocode for the body of method Counter.ping, again ignoring register save and restore.
interface Pingable {
    public void ping();
}
 
class Counter implements Pingable {
    int count = 0;
    public void ping() {
        ++count;
    }
    public int val() {
        return count;
    }
}
 
public class Ping {
    public static void main(String args[]) {
        Counter c = new Counter();
        c.ping();
        c.ping();
        int v = c.val();
        System.out.println(v);
    }
}
  1. In Ruby, as in Java 8 or Scala, an interface mix-in can provide method code as well as signatures. It cannot provide data members, because that would be multiple inheritance. Explain why dynamic typing makes this feature more powerful than it is in the other languages.
  2. 10.23-10.31 In More Depth.