Object-Oriented Programming
I. Inheritance: The sharing of an interface among a group of related
classes. One class is the superclass and declares the interface.
The remaining classes are subclasses of this superclass and
inherit the superclass's interface.
A. Examples
1. Graphical Objects: A graphical library might provide a
programmer with a number of objects, including rectangles,
lines, bitmaps, and text objects. These objects share
a number of common methods, such as:
a. draw: draws the object on the screen.
b. pointInObj: takes a point and returns true if the point
is in the object and false otherwise. Useful for
determining if the mouse cursor is over an object.
c. getBounds: returns the smallest rectangle that encloses
the object. Useful for deleting objects since the easist
way to erase an object is to draw an opaque rectangle
that is just large enough to cover the object.
One might declare a superclass object called GraphicalObject
and define its interface to include draw, pointInObj, and
getBounds. One would then declare Rectangle, Line, Text, and
Bitmap to be subclasses of GraphicalObject. Each of these
subclasses would inherit GraphicalObject's interface (i.e.,
they share it)
2. Data Structures: A data structure can often be implemented
in a number of different ways but each implementation should
share the same interface so the implementations can be swapped
without rewriting the application. This goal can be
accomplished by declaring a superclass for the data structure
that defines its interface and then declaring subclasses
that inherit the interface and which provide different
implementations. For example, one might declare a Stack class
that has the methods pop, push, isEmpty, and top. One would
then declare StackArray and StackList to be subclasses of
Stack. StackArray would implement a Stack using an array and
StackList would implement a Stack using a list. By virtue of
inheritance both implementations share the same interface and
hence objects of the two subclasses can be interchanged in
a program.
B. Polymorphism: Each subclass is allowed to implement its methods
differently, although the methods are supposed to have the same
result. For example, the draw method is supposed to render an
object on the screen but each subclass of a GraphicalObject will
implement this method differently. Similarly, the pointInObj method
should indicate whether a point intersects the object but each
subclass may implement this method differently. In the object-oriented
programming community, the term polymorphism is used to
describe this ability to have a method accomplish a certain result
but to be implemented in many different ways.
C. Extending An Interface: A subclass is allowed to add to the
interface it inherits but it is not allowed to subtract from it.
For example, a Line class probably wants to add a setX1, setY1,
setX2, and setY2 method to its interface so that a user can
set its endpoints. Similarly a Text class wants to add a setString
method that sets the string that a text object should display.
Finally a Bitmap class might add a loadFile method and a stretch
method that allows a user to specify the bitmap that should be
displayed and that allows a bitmap to be distorted. Since a subclass
cannot subtract from its interface, it must implement all
the methods that it inherits plus any methods it chooses to add to
the interface.
D. Types and Subtypes
1. A type is defined as a name and an interface that
provides a set of operations that can be performed on that
type. For example, an integer is a type that supports operations
such as +, -, *, / and %. The class GraphicalObject is a type
that supports the interface {draw, pointInObj, getBounds}).
a. A type does not specify an implementation but it does specify
the outcome of its operations.
b. Types pre-defined by the language are called primitive
types. Types defined by the user are called user-defined
types.
2. A subtype is a type that extends another type's
interface. It implements all the methods defined by
the type, plus potentially one or more additional methods.
For example, a Line is considered a subtype of GraphicalObject.
It implements the draw, pointInObj, and getBounds methods,
as well as other methods, such as setX1, setY1, setX2, and
set Y2.
3. In general superclasses define types and subclasses define
subtypes.
II. Benefit of Inheritance: By permitting polymorphism, inheritance
allows a group of related objects to be handled interchangeably, even
though they are implemented differently. The reason they can be
handled interchangeably is that the objects support the same operations
and these operations will have the same outcome, regardless of how
they are implemented. Specifically, objects may be handled
interchangeably because a pointer to a class X may, in addition to
pointing to an object of class X, point to any object which is a subclass
of X. For example, if we declare obj_ptr as follows:
GraphicalObject* obj_ptr;
then obj_ptr can point to either a GraphicalObject or a
Rectangle, Line, Bitmap, or text object.
A. The ability of a superclass pointer to also point to
subclass objects allows:
1. related objects with different subclasses to be stored
in the same list, stack, tree, etc. For example a drawing
program can store a group of rectangles, lines, bitmaps,
and text objects in one list rather than having to store
them in four separate lists. The drawing program can
traverse the list and be confident that it can call the
same method, for example a draw method, on each element
in the list.
2. any object which is an instance of a subclass of X to
be passed to a function expecting an instance of X.
For example, suppose we have the function declaration:
void addToClipRegion(GraphicalObject *a);
Not only can we pass GraphicalObjects to addToClipRegion,
we can also pass Rectangles, Lines, Bitmaps, and Text
objects to addToClipRegion. addToClipRegion might add
the objects bounding rectangle to an existing clip region
using the getBounds method. A window system uses a clip
region to only draw the parts of an object that intersect
the clip region. For example, if part of a window
becomes uncovered as a result of moving another window,
the window system only wants to draw the parts of objects
that were covered by the moved window. Hence it would
use a clip region equal in size to the moved window.
B. The reason a pointer to a class may point to a subclass is
that it is guaranteed that any method implemented by
the class is also implemented by the subclass. For example,
when we declare obj_ptr to be of type GraphicalObject, we
are permitted to invoke the methods draw,pointInObj, and
getBounds. Since any subclass of GraphicalObject also
implements these three methods, it is safe to allow obj_ptr
to point to any subclass.
C. If a pointer to class X points to an object of subclass Y, then
the pointer can only invoke methods in X's interface. Any
additional methods defined by Y cannot be accessed through
the pointer. For example, if obj_ptr points to a Line,
it cannot invoke setX1 or setY1, since these
methods are not in GraphicalObject's interface.
i. You can gain access to the sub-types methods by
downcasting. A downcast explicitly tells
the compiler that you know that the object currently
being pointed at has a particular type. A downcast
is a risky operation because if you are wrong, your
program will most likely core dump. Downcasting is
generally frowned upon, and is almost always
unnecessary. Typically if you know that a variable
points to an object with a particular type, you could
have simply declared the object to have that type,
thus obviating the need for a downcast.
Example: I make the following definition and then
use a downcast to call the setX1 method:
GraphicalObject *g = new Line();
((Line *)g)->setX1(10);
Since I know that g points to a line, I should
have declared g to be a line to start with. Then
I would not have had to do the downcast. In other
words, the following code is preferable:
Line *g = new Line();
g->setX1(10);
D. A pointer to a subclass may not point to a superclass object. For
example, the compiler will flag the following statement:
Line *l = new GraphicalObject();
The reason for this restriction is that a superclass is not guaranteed
to implement all of the subclass's methods. For example, when we
declare l to be a pointer to Line, that means that l is entitled
to call the setX1 method. However, a GraphicalObject does not
implement the setX1 method. Hence l cannot point to a GraphicalObject.
III. Syntax
A. To derive a class from another class, use the following
syntax:
class subclass_name : public super_class_name { ... }
For example:
class Rectangle : public GraphicalObject { ... }
1. A super class is also called a base class
2. A sub class is also called a derived class
3. If you forget to use the keyword public, then C++ will
use private by default. This will cause problems that you
don't want to deal with unless you are an expert C++ user.
Basically, public means you inherit the interface and
the implementation of the super class, while private means
you inherit the implementation but not the interface. For
more details, see the book.
B. Virtual methods: Indicates that sub classes will
specialize this method.
1. Syntax: Affix the keyword virtual to a method declaration:
class GraphicalObject {
virtual void draw();
};
2. A method is virtual if it is declared virtual or if
a super class declares it virtual
3. A method's signature in the subclass must exactly match
the method's signature in the super class for the
method to be considered the same as the method in
the super class.
a. If you alter the number of parameters, the method will
be considered different (it will be considered to overload
the method rather than to redefine the method).
b. You can never alter the return type of a virtual method.
The compiler will not consider the altered method to be
a different method, it will simply consider it to be an
error.
Example:
class Stack {
virtual void pop ();
virtual push(void *);
};
class IntStack {
void pop(); // overrides Stack's pop method
int pop(); // error: cannot alter return type
void push(int); // overloads push
};
Stack *stack;
stack = new IntStack();
stack->push(10); // error: Stack does not have a push
// method that takes an int
stack->pop(); // calls IntStack's void pop method
c. The restriction on not being able to overload the return
types makes it impossible to define a generic data structure
and then have subclasses specialize it for specific types
like ints, strings, etc. To overcome this difficulty C++
has a concept called templates which we will briefly cover
later in the course.
3. There are three different categories of implementation inheritance
a. Non-virtual method: If a method is declared non-virtual,
then the designer's intention is that the method's
implementation be inherited by all sub-classes.
b. Virtual method with a default implementation: if a method
is declared virtual and given a default implementation,
then the method's implementation will be inherited unless
a subclass overrides the implementation.
c. Pure virtual method: If a method is declare virtual and not
given a default implementation, then all subclasses must
define the implementation.
i. A pure virtual method is declared by appending "= 0" to
the declaration:
virtual void draw() = 0;
ii. If any methods in a class are declared pure virtual, then
the class is considered an abstract super class.
Concrete instances of the class cannot be created at
run-time, because the specification of the class is
incomplete.
4. A virtual declaration indicates to C++ that it must
wait until run-time to determine which method it will use.
Example:
class GraphicalObject {
virtual void draw();
void set_bounding_box(int, int, int, int);
};
class Rectangle: public GraphicalObject {
void draw();
// you should not do this because set_bounding_box
// was not declared virtual. I am doing this for
// illustrative purposes only.
void set_bounding_box(int, int, int, int);
};
GraphicalObject* obj_ptr;
obj_ptr = new GraphicalObject;
obj_ptr->draw(); // calls the draw method for GraphicalObject
obj_ptr = new Rectangle;
obj_ptr->draw(); // calls the draw method for Rectangle
// calls the set_bounding_box method for GraphicalObject
// because set_bounding_box is not declared virtual. Hence
// the call will be resolved at compile time. Since obj_ptr
// points to GraphicalObject, the call resolves to
// GraphicalObject's set_bounding_box method
obj_ptr->set_bounding_box(10, 10, 40, 40);
5. When to declare an inherited method and when not to declare
an inherited method in a subclass.
a. If you decide to provide an implementation for an inherited
method in a subclass, then you must declare the inherited
method in that subclass, even though you also declared it in the
superclass.
b. If you do not provide an implementation for an inherited
method (because you are inheriting its implementation
from a superclass), then you must not declare the
method in the subclass.
6. Make destructors virtual in super classes
a. Unlike other virtual methods, destructors have different
names (e.g., ~GraphicalObject and ~Rectangle). Nonetheless,
C++ considers destructors to be the same method.
Consequently, you must declare them virtual if you
want the right destructor to be called.
b. Unlike other virtual methods, C++ will call destructors
all the way up the inheritance hierarchy (e.g., if it calls
Rectangle's destructor, it will also call GraphicalObject's
destructor).
c. You cannot declare a destructor to be pure virtual. It
must have a default implementation, even if it is only
{}. For example:
virtual ~GraphicalObject() = 0; // won't work!!
virtual ~GraphicalObject() {} // fine
If you declare a destructor pure virtual, or you simply
forget to provide a default implementation, the compiler
won't complain but the linker will.
d. If you see a linker error about __NumberClassName
(e.g., __14ExpressionNode), you have forgotten
to define an implementation for a destructor or
a constructor
Example: If I forget to include a definition for
~GraphicalNode(), I get the linker errors:
Undefined first referenced
symbol in file
__14GraphicalNode /var/tmp/cca004XC1.o
_._14GraphicalNode /var/tmp/cca004XC1.o
IV. Some Thoughts on Defining Class Hierarchies
A. Inheritance should be used to share interfaces, not implementation.
It is nice if you luck out and get to share some implementation
but your only consideration in deciding whether to use inheritance
is whether the subclasses share a common interface.
B. In this class we will primarily use inheritance when we need
to store similar types of objects (e.g., events in a simulation,
graphical objects in a user interface) in a data structure,
such as a priority queue, stack, etc.
C. If you want to share implementation, it is often better to use
object composition rather than inheritance. For example, if
you want a stack to be implemented in terms of an array, it
is better to define a stack to have a pointer to an array object
than to define a stack to inherit from an array object. The
reason that a stack should not inherit from an array is that
the interfaces for the two objects are different. For example,
the extendible StringArray presented earlier in the semester
supports the methods get_value and set_value. You do not want
a Stack to support these two methods so you should not have
a Stack inherit from StringArray. Instead you should make the
Stack have a StringArray object. To emphasize the distinction between
the inheritance of interfaces and the re-use of implementation.
the object-oriented programming community defines is-a
and has-a relationships.
1. An is-a relationship means that two classes have a shared
interface. In other words, one class is a subclass of the
other class.
2. A has-a relationship means that one class uses another class
as a part. It often means that one class uses the implementation
of the other class.