C++ Programming/Classes/Abstract Classes
From Wikibooks, the open-content textbooks collection
< C++ Programming | Classes
Jump to: navigation, search
[edit] Abstract Classes
An abstract class is, conceptually, a class that cannot be instantiated and is usually implemented
as a class that has one or more pure virtual (abstract) functions.
A pure virtual function is one which must be overridden by any concrete (i.e., non-abstract)
derived class. This is indicated in the declaration with the syntax " = 0" in the member function's
declaration.
Example
class AbstractClass {
public:
virtual void AbstractMemberFunction() = 0; //pure
virtual function makes this class Abstract class
virtual void NonAbstractMemberFunction1();
//virtual function
void NonAbstractMemberFunction2();
};
In general an abstract class is used to define an implementation and is intended to be inherited
from by concrete classes. It's a way of forcing a contract between the class designer and the users
of that class. If we wish to create a concrete class (a class that can be instantiated) from an
abstract class we must declare and define a matching member function for each abstract member
function of the base class. Otherwise we will create a new abstract class (this could be useful
sometimes).
Sometimes we use the phrase "pure abstract class," meaning a class that exclusively has pure
virtual functions (and no data). The concept of interface is mapped to pure abstract classes in C+
+, as there is no construction "interface" in C++ the same way that there is in Java.
Example
class Vehicle {
public:
explicit
Vehicle( int topSpeed )
: m_topSpeed( topSpeed )
{}
int TopSpeed() const {
return m_topSpeed;
}
virtual void Save( std::ostream& ) const = 0;
private:
int m_topSpeed;
};
class WheeledLandVehicle : public Vehicle {
public:
WheeledLandVehicle( int topSpeed, int
numberOfWheels )
: Vehicle( topSpeed ),
m_numberOfWheels( numberOfWheels )
{}
int NumberOfWheels() const {
return m_numberOfWheels;
}
void Save( std::ostream& ) const; // is
implicitly virtual
private:
int m_numberOfWheels;
};
class TrackedLandVehicle : public Vehicle {
public:
int TrackedLandVehicle ( int topSpeed, int
numberOfTracks )
: Vehicle( topSpeed), m_numberOfTracks
( numberOfTracks )
{}
int NumberOfTracks() const {
return m_numberOfTracks;
}
void Save( std::ostream& ) const; // is
implicitly virtual
private:
int m_numberOfTracks;
};
In this example the Vehicle is an abstract base class as it has an abstract member function. It is
not a pure abstract class as it also has data and concrete member functions. The class
WheeledLandVehicle is derived from the base class. It also holds data which is common to all
wheeled land vehicles, namely the number of wheels. The class TrackedLandVehicle is another
variation of the Vehicle class.
This is something of a contrived example but it does show how that you can share
implementation details among a hierarchy of classes. Each class further refines a concept. This is
not always the best way to implement an interface but in some cases it works very well. As a
guideline, for ease of maintenance and understanding you should try to limit the inheritance to no
more than 3 levels. Often the best set of classes to use is a pure virtual abstract base class to
define a common interface. Then use an abstract class to further refine an implementation for a
set of concrete classes and lastly define the set of concrete classes.
[edit] Pure Abstract Classes
An abstract class is one in which there is a declaration but no definition for a member function.
The way this concept is expressed in C++ is to have the member function declaration assigned to
zero.
Example
class PureAbstractClass
{
public:
virtual void AbstractMemberFunction() = 0;
};
A pure Abstract class has only abstract member functions and no data or concrete member
functions. In general, an abstract class is used to define an interface and is intended to be
inherited by concrete classes. It's a way of forcing a contract between the class designer and the
users of that class. The users of this class must declare a matching member function for the class
to compile.
Example of usage for a pure Abstract Class
class DrawableObject
{
public:
virtual void Draw(GraphicalDrawingBoard&) const =
0; //draw to GraphicalDrawingBoard
};
class Triangle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a
triangle
};
class Rectangle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a
rectangle
};
class Circle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a
circle
};
typedef std::list<DrawableObject*> DrawableList_t;
DrawableList_t drawableList;
GraphicalDrawingBoard gdrawb;
drawableList.pushback(new Triangle());
drawableList.pushback(new Rectangle());
drawableList.pushback(new Circle());
for(DrawableList_t::const_iterator iter =
drawableList.begin(),
endIter = drawableList.end();
iter != endIter;
++iter)
{
DrawableObject *object = *iter;
object->Draw(gdrawb);
}
Note that this is a bit of a contrived example and that the drawable objects are not fully defined
(no constructors or data) but it should give you the general idea of the power of defining an
interface. Once the objects are constructed, the code that calls the interface does not know any of
the implementation details of the called objects, only that of the interface. The object
GraphicalDrawingBoard is a placeholder meant to represent the thing onto which the object will
be drawn, i.e. the video memory, drawing buffer, printer.
Note that there is a great temptation to add concrete member functions and data to pure abstract
base classes. This must be resisted and in general it is a sign that the interface is not well
factored. Data and concrete member functions tend to imply a particular implementation and as
such can inherit from the interface but should not be that interface. Instead if there is some
commonality between concrete classes, creation of abstract class which inherits its interface from
the pure abstract class and defines the common data and member functions of the concrete
classes works well. Some care should be taken to decide whether inheritance or aggregation
should be used. Too many layers of inheritance can make the maintenance and usage of a class
difficult. Generally, the maximum accepted layers of inheritance is about 3, above that and
refactoring of the classes is generally called for. A general test is the "is a" vs "has a", as in a
Square is a Rectangle, but a Square has a set of sides.