CPSC 427: Object-Oriented Programming
Michael J. Fischer
Linear Container Design
Overview of linear container example We’ve seen three closely related designs for linear containers:
Common to all three examples is the use of a linked list of Cell objects to implement various kinds of containers in which to store data objects of type Exam.
Item definitions
These designs choose different ways of associating the application-specific class Exam with the generic data field contained in a linked list Cell.
Differences in functionality
19-Virtual implements a FIFO class Stack and a LIFO class
Queue of unordered Exams.
22b-Multiple and 22c-Multiple-template add a key() method to Exam and implement two new data structures that make use of the key:
Class structure
The class structure of the three implementations are similar as are the main programs. All have have the following classes:
In 19-Virtual, Item and Exam are synonymous.
In 22b-Multiple and 22c-Multiple-template, Item is derived from Exam and extends the comparison operators < and ==.
Template structure
22c-Multiple-template adds a template parameter class T to
represent the application-specific user object type.
In the sample application, main() instantiates the template as
List<Item> and PQueue<Item>.
As before, Item is derived from Exam.
In order to create linear containers for a new user type MyData, one would have to modify Item to derive from MyData instead of from Exam.
Further extensions
Can we make Item a template class, e.g.,
Then the user with application-specific class MyData could just use Item<MyData> wherever 22c-Multiple-template uses Item.
Two problems
Two problems must be overcome to make this approach work:
Defining KeyType
Problem 1 can be solved by putting a typedef for KeyType into class MyClass. This type name can be used as follows:
Constructing the data elements
Problem 2 is not so easily solved. Some possibilities:
This adds a time penalty and a requirement that T be copyable.
23a-Multiple-template
This second idea is implemented in example 23a-Multiple-template.
A consequence of this design is that main() now mentions only the user type (Exam), not Item, effectively isolating the user interface from the underlying implementation, e.g.,
STL and Polymorphism
Derivation
from
STL
containers
Common wisdom on the internet says not to inherit from STL
containers.
For example, http://en.wikipedia.org/wiki/Standard_Template_Library
says, This reflects Rule 35 of Sutter and Alexandrescu,
Replacing
authority
with
understanding
C++ is a complicated and powerful language. Some constructs such as classes are used for several different
purposes. What is appropriate in one context may not be in another. Simple rules will not make you a good C++ programmer. Thought,
understanding, and experience will.
Two
kinds
of
derivation
C++ supports two distinct kinds of derivation:
We say B is derived from A, and B inherits members from A. Each B object has an A object embedded within it. The derivation is simple if no members of A are virtual; otherwise it is
polymorphic.
How
are
they
the
same?
With both kinds of derivation, a function in the derived class B can
override a function in the base class A.
In both cases, one can create and delete objects of class B.
Both A’s and B’s destructor are called when a B object is deleted.
What
is
simple
derivation
good
for?
Some uses for simple derivation.
With simple derivation, the derived class is the public interface. Often protected or private derivation is used to hide the base class
from the users of the derived class.
What
are
the
problems
with
simple
derivation?
What
is
polymorphic
derivation
good
for?
Some uses for polymorphic derivation.
What
are
the
problems
of
polymorphic
derivation?
Every polymorphic base class (containing even one virtual function) adds
a runtime type tag to each instance. This adds cost in both time and space.
Contrasts
between
simple
and
polymorphic
derivation
Simple derivation:
Polymorphic derivation:
Containment
as
an
alternative
to
simple
derivation
Often the same class can be implemented using either containment or
derivation. Derivation:
A’s public member functions are inherited by B. Containment:
Access to A’s public member functions requires a “pass-through”
function for delegation.
Argument
for
containment
Containment is a more distant relationship than derivation. Less coupling between classes is safer and less error-prone. Using containment, derived class is explicit about what is exported. For more info, see http://www.gotw.ca/publications/mill06.htm.
STL
container
as
a
base
class
We apply these concepts to STL base classes. Base classes are simple, not polymorphic (no virtual functions, no virtual
destructor). This means that they should only be used with simple derivation. They
are not suitable as base classes for polymorphic derivation. Often containment is preferable, but the large number of member
functions that many STL classes support makes it cumbersome to get
the same degree of functionality in the derived class as comes “for free”
with derivation.
Can
I
turn
an
STL
container
into
a
polymorphic
base
class?
Yes, sort of. Here’s the idea.
A
polymorphic
base
class
MyVectorInt is a polymorphic base class with virtual destructor and can
be used as such.
Dynamic
cast
It is always okay to cast a derived class pointer to a base class pointer,
as in the previous example. The reverse is only semantically meaningful if the allocated type
of the object actually is the type to which it is being cast. In
that case, one can use a dynamic_cast to effect the conversion.
dynamic_cast returns nullptr if p is pointing to something that does
not have dynamic type Derived*.
Design Patterns General
OO
principles
class B : public A { ... };
class A { public:
~A() { std::cout << "A’s destructor called" << std::endl; }
};
class B: public A { public:
~B() { std::cout << "B’s destructor called" << std::endl; }
};
int main() { B bobj; }
Output: B’s destructor called
A’s destructor called
class B: public A { ... g() { f() ... } };
class B { private: A a; ... g() { a.f() ... }
public: f() { return a.f(); } };
#include <vector>
using namespace std;
class MyVectorInt : public vector<int> {
public:
MyVectorInt() : vector<int>() {}
virtual ~MyVectorInt() {
cout << "Base class destructor is called" << endl; }
};
class Derived : public MyVectorInt {
public:
~Derived() {
cout << "Derived destructor is called" << endl; }
};
MyVectorInt* p; // a polymorphic pointer
Derived* obj = new Derived(); // a derived object
p = obj; // ok to assign
delete p; // ok to delete; destructors called
}
Derived* q;
...
q = dynamic_cast<Derived*>(p);