Inheritance — Abstract Base Classes (ABCs)
What’s the big deal of separating interface from implementation?
Interfaces are a company’s most valuable resources. Designing an interface takes longer than whipping together a concrete class which fulfills that interface. Furthermore interfaces require the time of more expensive people.
Since interfaces are so valuable, they should be protected from being tarnished by data structures and other implementation artifacts. Thus you should separate interface from implementation.
How do I separate interface from implementation in C++ (like Modula-2)?
Use an ABC.
What is an ABC?
An abstract base class.
At the design level, an abstract base class (ABC) corresponds to an abstract concept. If you asked a mechanic if he
repaired vehicles, he’d probably wonder what kind-of vehicle you had in mind. Chances are he doesn’t repair space
shuttles, ocean liners, bicycles, or nuclear submarines. The problem is that the term “vehicle” is an abstract concept
(e.g., you can’t build a “vehicle” unless you know what kind of vehicle to build). In C++, class
Vehicle
would be
an ABC, with Bicycle
, SpaceShuttle
, etc, being derived classes (an OceanLiner
is-a-kind-of-a Vehicle
). In
real-world OO, ABCs show up all over the place.
At the programming language level, an ABC is a class
that has one or more pure virtual member
functions. You cannot make an object (instance) of an ABC.
What is a “pure virtual” member function?
A member function declaration that turns a normal class into an abstract class (i.e., an ABC). You normally only implement it in a derived class.
Some member functions exist in concept; they don’t have any reasonable definition. E.g., suppose I asked you to draw a
Shape
at location (x,y)
that has size 7. You’d ask me “what kind of shape should I draw?” (circles, squares,
hexagons, etc, are drawn differently). In C++, we must indicate the existence of the draw()
member function (so
users can call it when they have a Shape*
or a Shape&
), but we recognize it can (logically) be defined only in
derived classes:
class Shape {
public:
virtual void draw() const = 0; // = 0 means it is "pure virtual"
// ...
};
This pure virtual function makes Shape
an ABC. If you want, you can think of the “= 0;
” syntax as if the code were
at the NULL
pointer. Thus Shape
promises a service to its users, yet Shape isn’t able to provide any code to fulfill
that promise. This forces any actual object created from a [concrete] class derived from Shape to have the indicated
member function, even though the base class doesn’t have enough information to actually define it yet.
Note that it is possible to provide a definition for a pure virtual function, but this usually confuses novices and is best avoided until later.
How do you define a copy constructor or assignment operator
for a class that contains a pointer to a (abstract) base class?
If the class “owns” the object pointed to by the (abstract) base class pointer, use the Virtual Constructor
Idiom in the (abstract) base class. As usual with this idiom, we declare a pure
virtual
clone()
method in the base class:
class Shape {
public:
// ...
virtual Shape* clone() const = 0; // The Virtual (Copy) Constructor
// ...
};
Then we implement this clone()
method in each derived class. Here is the code for derived class Circle
:
class Circle : public Shape {
public:
// ...
virtual Circle* clone() const;
// ...
};
Circle* Circle::clone() const
{
return new Circle(*this);
}
(Note: the return type in the derived class is intentionally different from the one in the base class.)
Here is the code for derived class Square
:
class Square : public Shape {
public:
// ...
virtual Square* clone() const;
// ...
};
Square* Square::clone() const
{
return new Square(*this);
}
Now suppose that each Fred
object “has-a” Shape
object. Naturally the Fred
object doesn’t know whether the
Shape
is Circle
or a Square
or … Fred
’s copy constructor and assignment operator
will invoke Shape
’s
clone()
method to copy the object:
class Fred {
public:
// p must be a pointer returned by new; it must not be NULL
Fred(Shape* p)
: p_(p) { assert(p != NULL); }
~Fred()
{ delete p_; }
Fred(const Fred& f)
: p_(f.p_->clone()) { }
Fred& operator= (const Fred& f)
{
if (this != &f) { // Check for self-assignment
Shape* p2 = f.p_->clone(); // Create the new one FIRST...
delete p_; // ...THEN delete the old one
p_ = p2;
}
return *this;
}
// ...
private:
Shape* p_;
};