Back to July Calendar 

Previous Entry Next Entry→

September 11, 2005

The proposed elimination of the estate tax by pseudo academic institutions such as the Heritage Foundation, who spend great tax-deductible efforts at renaming this as the "death tax" when, in fact, death is the inevitable negative, whereas inheritance is the more tangible fungible.  Paris Hilton wouldn't want to surrender more than her handlers would want.  The rich only find fulfillment by amassing more riches.

Be that as it may?

Inheritance allows us to specialize the behavior of a class. For example, a Shape class may provide basic functionality for managing 2D shapes. The Shape class has member variables for the centroid and area. The Shape class can be specified to provide functionality for a particular shape, such as a circle which inherits the properties and methods of Shape.

 class Circle : public Shape {
        ...
    };

The Circle class adds a new member variable for the radius. When a Circle object is instantiated, it will include a radius variable in addition to inherited centroid and area variables. The Circle object can also call methods associated with class Shape, such as get_Centroid(). The Shape class is the base class here and Circle is the derived class.

Members of class Shape that are private (e.g. mCentroid) cannot be directly accessed from within the Circle class definition. However, they can be accessed indirectly through the Shape class's public interface (e.g. get_Centroid()).  If we wish to allow class Circle to directly access members of class Shape, those members should be made protected (e.g. mfArea). To the outside world, i.e. the scope of main(), protected members behave in exactly the same way as private members--the difference is in the ability of derived classes to access protected members of the super class.

It is possible to use a base class pointer to address a derived class object, e.g.

    Circle *pc
    Shape *ps;

    pc = new Circle();
    ps = pc;

This ability of the base class to be assigned the address of a derived class is known as polymorphism. The Shape pointer can access those methods of the Circle object that are inherited from Shape.  e.g.

    ps->get_Centroid()

The Circle class can also override functions that it inherits from the Shape class, as in the case of print(). To make this work, we must declare print() as a virtual function in class Shape. Then, when the Shape pointer accesses the print() function, as in

    ps->print();

the print() function in the underlying Circle object is invoked. In the example below, an array of Shape pointers, sa, is used to store a mixed collection of Circle and Rectangle objects. In the code fragment

    for (i = 0; i < num_shapes; i++) {
        sa[i]->print();   // This calls either Circle::print() or Rectangle::print()
    }

the decision to call the print() function in class Circle or the one in class Rectangle must be made at run-time through a mechanism known as dynamic binding.

The implementation of the print() function in class Shape serves as a default implementation, which will be used if the derived class chooses not to provide an overiding implementation. It is possible, however, for the Shape class to require all derived classes to provide an overriding implementation, as in the case of draw(). The draw() function is known as a pure virtual function, because it does not have an implementation in class Shape. Pure virtual functions have a declaration of the form

    virtual void draw() = 0;

Since we have not implemented draw() in class Shape, the class is incomplete and we cannot actually create Shape objects. The Shape class is therefore said to be an abstract base class.

Be careful when deleting the objects stored in the array of Shape pointers. For example, in the fragment here:

    for (i = 0; i < num_shapes; i++)
        // This calls either Circle::~Circle() or Rectangle::~Rectangle()
        delete sa[i]; 
        
// before calling Shape::~Shape().

delete is called on sa[i], which is a Shape pointer, even though the object that it points to is really a Circle or a Rectangle. To ensure that the appropriate Circle or Rectangle destructor is called, make the Shape destructor a virtual destructor.

shape.h

#ifndef _SHAPE_H_
#define _SHAPE_H_
#include
<iostream>
using namespace std;
#include "point.h"
#ifndef
DEBUG_PRINT
#ifdef _DEBUG
#define DEBUG_PRINT(str) cout << str << endl;
#else
#define DEBUG_PRINT(str)
#endif
#endif

class Shape {
    // The private members of the Shape class are only accessible within
    // the definition of class Shape. They are not accessible within
    // the definitions of classes derived from the Shape class, e.g. Circle,
    // or within main().
private:
    Point mCentroid;
    // The protected members of the Shape class are accessible within the
    // definition of class Shape. They are also accessible within the
    // definitions of classes derived immediately from the Shape class, e.g.
    // Circle. However, they are not accessible within main().

protected:
    float mfArea;
    // The public members of the Shape class are accessible everywhere i.e. in
    // the Shape class definition, in derived class definitions and in main().
public:
    Shape(float fX, float fY);
    virtual ~Shape();               // A virtual destructor.
    virtual void print();           // A virtual function.
    virtual void draw() = 0;        // A pure virtual function.
    const Point& get_centroid() {
        return mCentroid;
    }
};

#endif

=================================================

shape.C

#include "shape.h"

Shape::Shape(float fX, float fY) : mCentroid(fX, fY) {
    // We must use an initialization list to initialize mCentroid.
    // Here in the body of the constructor would be too late.

    DEBUG_PRINT("In constructor Shape::Shape(float, float)")
}
Shape::~Shape() {
    DEBUG_PRINT("In destructor Shape::~Shape()")
}
void Shape::print() {
    DEBUG_PRINT("In Shape::print()");
    cout << "Centroid: ";
    mCentroid.print();
    cout << "Area = " << mfArea << endl;
}

 

====================================

circle.h

#ifndef _CIRCLE_H_
#define _CIRCLE_H_
#include
"shape.h"
class
Circle : public Shape {
private:
    float mfRadius;
public:

    Circle(float fX=0, float fY=0, float fRadius=0);
    ~Circle();
    void print();
    void draw();
};

#endif

 

===================================

circle.C

#include "circle.h"
#define PI 3.1415926536
Circle::Circle(float fX, float fY, float fRadius) : Shape(fX, fY) {
    // use initialization list to initialize the Shape part of the Circle object.
    DEBUG_PRINT("In constructor Circle::Circle(float, float, float)")
    mfRadius = fRadius;
    mfArea = PI * fRadius * fRadius;  // mfArea is a protected member of class Shape.
}
Circle::~Circle() {
    DEBUG_PRINT("In destructor Circle::~Circle()")
}
void
Circle::print() {
    DEBUG_PRINT("In Circle::print()")
    cout << "Circle Radius: " << mfRadius << endl;
    // If we want to print out the Shape part of the Circle object as well,
    // we could call the base class print function like this:

    Shape::print();
}
void
Circle::draw() {
    // Assume that this draws the circle.
    DEBUG_PRINT("In Circle::draw()")
}
 

==================================

rectangle.h

#ifndef _RECTANGLE_H_
#define _RECTANGLE_H_
#include "shape.h"
class
Rectangle : public Shape {
private:
    float mfWidth, mfHeight;
public:
    Rectangle(float fX=0, float fY=0, float fWidth=1, float fHeight=1);
    ~Rectangle();
    void print();
    void draw();
};

#endif

 

===================================

rectangle.cpp

#include "rectangle.h"
Rectangle::Rectangle(float fX, float fY, float fWidth, float fHeight)  : Shape(fX, fY) {
    // use an initialization list to initialize the Shape part of the Rectangle object.
    DEBUG_PRINT("In constructor Rectangle::Rectangle(float, float, float, float)")
    mfWidth = fWidth;
    mfHeight = fHeight;
    mfArea = fWidth * fHeight;  // mfArea is a protected member of class Shape.
}
Rectangle::~Rectangle() {
    DEBUG_PRINT("In destructor Rectangle::~Rectangle()")
}
void Rectangle::print() {
    DEBUG_PRINT("In Rectangle::print()")
    cout << "Rectangle Width: " << mfWidth << " Height: " << mfHeight << endl;

    // If we want to print out the Shape part of the Rectangle object as well,
    // we could call the base class print function like this:
    Shape::print();
}

void Rectangle::draw() {
    // Assume that this draws the rectangle.
    DEBUG_PRINT("In Rectangle::draw()")
}

 

=====================================

myprog.C

#include "shape.h"
#include "circle.h"
#include "rectangle.h"
int main() {
    const int num_shapes = 5;
    int i;
    // Create an automatic Circle object.
    Circle c;

    // can't instantiate a Shape object if Shape class has a pure virtual function
    // i.e. a virtual function without a definition within class Shape. 
    // Class Shape is therefore said
    // to be an abstract base class.
    // Shape s;  // This is not allowed.

    // We are allowed to have Shape pointers, however.
    Shape *sa[num_shapes];      // Create an array of Shape pointers.
    // C++ allows use of a base class pointer to point to a derived class object.
    // This is known as polymorphism. We can thus store a heterogeneous collection
    // of Circles and Rectangles
    // using the array of Shape pointers.
  
  sa[0] = new Circle(2,3,1);
    sa[1] = new Rectangle(0,2,2,3);
    sa[2] = new Circle(7,6,3);
    sa[3] = new Circle(0,2,2);
    sa[4] = new Rectangle(4,3,1,1);
    // Print out all of the objects. We have made the print function virtual
    // in class shape. This means that it can be overridden by print functions
    // with a similar signature that are specific to the derived classes. If
    // a derived class does not provide an implementation of print, then the
    // Shape::print function will be called by default.
    for (i = 0; i < num_shapes; i++) {
        sa[i]->print();   
        // This will call either Circle::print() or Rectangle::print(), as appropriate.
   
}
    // Delete the objects. Note that we have called delete on Shape pointers,
    // even though the objects that we created using new were derived class
    // objects. To ensure that the appropriate destructor for the derived
    // object is called, we must make the Shape destructor virtual.
    for (i = 0; i < num_shapes; i++)
        delete sa[i];  // This will call either Circle::~Circle() or                 
                       // Rectangle::~Rectangle(), as appropriate,
                       // before calling Shape::~Shape().
    return 0;
}

Here is the output of this program:

In constructor Point::Point(float fX, float fY)
In constructor Shape::Shape(float, float)
In constructor Circle::Circle(float, float, float)
In constructor Point::Point(float fX, float fY)
In constructor Shape::Shape(float, float)
In constructor Circle::Circle(float, float, float)
In constructor Point::Point(float fX, float fY)
In constructor Shape::Shape(float, float)
In constructor Rectangle::Rectangle(float, float, float, float)
In constructor Point::Point(float fX, float fY)
In constructor Shape::Shape(float, float)
In constructor Circle::Circle(float, float, float)
In constructor Point::Point(float fX, float fY)
In constructor Shape::Shape(float, float)
In constructor Circle::Circle(float, float, float)
In constructor Point::Point(float fX, float fY)
In constructor Shape::Shape(float, float)
In constructor Rectangle::Rectangle(float, float, float, float)
In Circle::print()
Circle Radius: 1
In Shape::print()
Centroid: (2,3)
Area = 3.14159
In Rectangle::print()
Rectangle Width: 2 Height: 3
In Shape::print()
Centroid: (0,2)
Area = 6
In Circle::print()
Circle Radius: 3
In Shape::print()
Centroid: (7,6)
Area = 28.2743
In Circle::print()
Circle Radius: 2
In Shape::print()
Centroid: (0,2)
Area = 12.5664
In Rectangle::print()
Rectangle Width: 1 Height: 1
In Shape::print()
Centroid: (4,3)
Area = 1
In destructor Circle::~Circle()
In destructor Shape::~Shape()
In destructor Point::~Point()
In destructor Rectangle::~Rectangle()
In destructor Shape::~Shape()
In destructor Point::~Point()
In destructor Circle::~Circle()
In destructor Shape::~Shape()
In destructor Point::~Point()
In destructor Circle::~Circle()
In destructor Shape::~Shape()
In destructor Point::~Point()
In destructor Rectangle::~Rectangle()
In destructor Shape::~Shape()
In destructor Point::~Point()