Home

Class Abstraction

 

Overview

So far, we have been able to create classes and inherit from them. Here is an example of a simple class we created in the previous lesson:

Header File: Rectangle.h
#pragma once

using namespace System;

public ref class CRectangle
{
private:
    double len;
    double hgt;

public:
    property double Length
    {
	double get() { return len; }

	void set(double L)
	{
		if( L <= 0 )
			len = 0;
		else
			len = L;
	}
    }

    property double Height
    {
	double get() { return hgt; }

	void set(double h)
	{
		if( h <= 0 )
			hgt = 0;
		else
			hgt = h;
	}
    }

    property double Perimeter
    {
	double get() { return 2 * (Length + Height); }
    }

    property double Area
    {
	double get() { return Length * Height; }
    }

public:
	CRectangle();
	CRectangle(double length, double height);
	void CRectangle::ShowCharacteristics();
};
 

 

 

 

 

 

 

Rectangle

Source File: Rectangle.cpp
#include "Rectangle.h"

CRectangle::CRectangle()
    : len(0.00), hgt(0.00)
{
}

CRectangle::CRectangle(double length, double height)
    : len(length), hgt(height)
{
}

void CRectangle::ShowCharacteristics()
{
    Console::WriteLine(L"Rectangle Characteristics");
    Console::WriteLine(L"Length:    {0}", this->Length);
    Console::WriteLine(L"Height:    {0}", this->Height);
    Console::WriteLine(L"Perimeter: {0}", this->Perimeter);
    Console::WriteLine(L"Area:      {0}", this->Area);
}
Source File: Exercise.cpp
#include "Rectangle.h"

using namespace System;

int main()
{
    CRectangle ^ rect = gcnew CRectangle(18.64, 28.42);

    rect->ShowCharacteristics();

    Console::WriteLine();
    return 0;
}

This would produce:

Rectangle Characteristics
Length:    18.64
Height:    28.42
Perimeter: 94.12
Area:      529.7488

Press any key to continue . . .

Imagine you want to create a rectangular parallelepiped. Using the above CRectangle class, you certainly would not have to start from scratch. You can derive from this class and create a new one. When inheriting from a class, a base class such as the above CRectangle can be configured to provide its children with the basic foundation they would need. Although a child class can implement a new behavior not available on the parent class, sometimes the derived class will need a customized implementation of a behavior that has already been configured in its parent. For example, if you derive a box from a rectangle, since a box has 6 faces, when creating the area of the box, you certainly would not expect the have the same value as that of the parent.

Consider this:

Header File: Box.h
#pragma once

#include "Rectangle.h"

public ref class CBox : public CRectangle
{
private:
    double wdt;

public:
    property double Width
    {
	double get() { return wdt; }

	void set(double w)
	{
	    if( wdt <= 0 )
		wdt = 0;
	    else
		wdt = w;
	}
    }

    property double Area
    {
	double get()
	{
	    return 2 * ((Length * Height) +
		        (Length * Width) +
			(Height * Width));
	}
    }

    property double Volume
    {
	double get() { return Length * Height * Width; }
    }

public:
    CBox();
    CBox(double Length, double Height, double width);
    void ShowCharacteristics();
};
Box
Source File: Box.cpp
#include "Box.h"

CBox::CBox()
    : wdt(0.00)
{
}

CBox::CBox(double length, double height, double width)
    : CRectangle(length, height), wdt(width)
{
}

void CBox::ShowCharacteristics()
{
    Console::WriteLine(L"Box Characteristics");
    Console::WriteLine(L"Length:    {0}", this->Length);
    Console::WriteLine(L"Height:    {0}", this->Height);
    Console::WriteLine(L"Area:      {0}", this->Area);
    Console::WriteLine(L"Volume:    {0}", this->Volume);
}
 

Practical LearningPractical Learning: Introducing Class Abstraction

  1. Start Microsoft Visual C++ 2005
  2. On the main menu, click File -> New -> Project...
  3. On the left side, make sure that Visual C++ is selected. In the Templates list, click CLR Empty Project
  4. In the Name box, replace the string with ElectroStore6 and click OK
  5. On the main menu, click Project -> Add Class...
  6. In the Categories lists, expand Visual C++ and click C++.
    In the Templates list, make sure C++ Class is selected and click Add
  7. Set the Name of the class to CStoreItem and click Finish
  8. Complete the StoreItem.h header file as follows:
     
    #pragma once
    using namespace System;
    
    public enum class ItemsCategories
    {
        Unknown,
        CablesAndConnectors,
        CellPhonesAndAccessories,
        Headphones,
        DigitalCameras,
        PDAsAndAccessories,
        TelephonesAndAccessories,
        TVsAndVideos,
        SurgeProtectors,
        Instructional
    };
    
    namespace ElectronicsStore
    {
        public ref class CStoreItem
        {
        public:
            // An item whose characteristics are not (yet) defined
            CStoreItem(void);
            // An item that is known by its make, model, and unit price
            CStoreItem(long itmNbr, String ^ make,
    		   String ^ model, double unitPrice);
            // An item that is known by its name and unit price
            CStoreItem(long itmNbr, String ^ name, double unitPrice);
            // An item completely defined
            CStoreItem(long itmNbr, ItemsCategories category,
    	           String ^ make, String ^ model, double unitPrice);
            ~CStoreItem();
    
        private:
            long            nbr;
            ItemsCategories cat;
            String        ^ mk;
            String        ^ mdl;
            String        ^ nm;
            double          price;
    
        public:
            property long ItemNumber
            {
                long get() { return nbr; }
                void set(long n) { this->nbr = n; }
            }
    
            property ItemsCategories Category
            {
                ItemsCategories get() { return cat; }
    	        void set(ItemsCategories c) { this->cat = c; }
            }
    
            property String ^ Make
            {
    	    String ^ get() { return mk; }
    	    void set(String ^ m) { this->mk = m; }
            }
    
            property String ^ Model
            {
    	    String ^ get() { return mdl; }
    	    void set(String ^ m) { this->mdl = m; }
            }
    
            property String ^ Name
            {
                String ^ get() { return nm; }
    	    void set(String ^ n) { this->nm = n; }
            }
    
            property double UnitPrice
            {
    	    double get() { return price; }
    	    void set(double p) { this->price = p; }
            }
        };
    }
  9. To create a source file, on the main menu, click Project -> Add New Item...
  10. In the Templates list, click C++ File (.cpp)
  11. Set the Name to StoreItem and press Enter
  12. Complete the file as follows:
     
    #include "StoreItem.h"
    
    namespace ElectronicsStore
    {
        CStoreItem::CStoreItem(void)
        {
            nbr      = 0;
    		cat      = ItemsCategories::Unknown;
            mk       = L"Unknown";
            mdl      = L"Unspecified";
            nm       = L"N/A";
            price    = 0.00;
        }
    
        CStoreItem::CStoreItem(long itmNbr, String ^ make,
    			   String ^ model, double unitPrice)
        {
            nbr      = itmNbr;
            cat      = ItemsCategories::Unknown;
            mk       = make;
            mdl      = model;
            nm       = L"N/A";
            price    = unitPrice;
        }
        
        CStoreItem::CStoreItem(long itmNbr, String ^ name,
    			   double unitPrice)
        {
            nbr      = itmNbr;
            cat      = ItemsCategories::Unknown;
            mk       = L"Unknown";
            mdl      = L"Unspecified";
            nm       = name;
            price    = unitPrice;
        }
        
        CStoreItem::CStoreItem(long itmNbr, ItemsCategories category,
    			   String ^ make, String ^ model,
    			   double unitPrice)
        {
            nbr      = itmNbr;
            cat      = category;
            mk       = make;
            mdl      = model;
            price    = unitPrice;
        }
    
        CStoreItem::~CStoreItem()
        {
        }
    }
  13. To create one more source file, on the main menu, click Project -> Add New Item...
  14. In the Templates list, make sure C++ File (.cpp) is selected.
    Set the Name to Exercise and click Add
  15. Complete the file as follows:
     
    #include "StoreItem.h"
    
    using namespace System;
    
    int main()
    {
        String ^ strTitle = L"=-= Nearson Electonics =-=\n"
    		        L"******* Store Items ******";
    
        Console::WriteLine();
        return 0;
    }
  16. Execute the application to make sure it can compile
  17. Close the DOS window

Virtual Members

When studying inheritance, we learned that there is a special bond between an inherited class and its parent. Not only does the child object have access to the public members of a class but also the child, based on this relationship, has direct access to the members of the protected section(s) of the parent. If there is such a good relationship between a class and its children, it should be possible to access a members of a derived class using an instance of the parent. Consider this:

Source File:: Exercise.cpp
#include "Rectangle.h"
#include "Box.h"

using namespace System;

int main()
{
    CBox ^ box = gcnew CBox(18.64, 28.42, 40.08);

    CRectangle ^ rect = box;

    rect->ShowCharacteristics();
    Console::WriteLine();
    box->ShowCharacteristics();

    Console::WriteLine();
    return 0;
}

This would produce:

Rectangle Characteristics
Length:    18.64
Height:    28.42
Perimeter: 94.12
Area:      529.7488

Box Characteristics
Length:    18.64
Height:    28.42
Area:      4831.8272
Volume:    21232.331904

Press any key to continue . . .

Notice that, although the rect handle is assigned an instance of its child, this parent object can access the properties of a child variable and is able to show only the characteristics that belongs to it, namely Length and Height. The Width, that is part of the parent class CRectangle is ignored. Also notice that the box is able to appropriately show its characteristics.

Notice that both the parent class CRectangle and the child class CBox have each a property named Area and a method named ShowCharacteristics. After creating a handle of a child class and assigning it a handle of a parent, when we access a property they share or when accessing a method that each has, what version of the property or of the method are we accessing?

A virtual property or method is a member that a parent has and that a child implements also. When creating a parent class, you should put a flag on a property or method that a child of that class would also have. To create a virtual method (or property), precede its return type (or the property keyword) with the virtual keyword. Here is an example:

Header File: Rectangle.h
#pragma once

using namespace System;

public ref class CRectangle
{
protected:
    double len;
    double hgt;

public:
    property double Length
    {
	double get() { return len; }

	void set(double L)
	{
	    if( L <= 0 )
		len = 0;
	    else
		len = L;
	}
    }

    property double Height
    {
	double get() { return hgt; }

	void set(double h)
	{
	    if( h <= 0 )
		hgt = 0;
	    else
		hgt = h;
	}
    }

    property double Perimeter
    {
	double get() { return 2 * (Length + Height); }
    }

    virtual property double Area
    {
	double get() { return Length * Height; }
    }

public:
    CRectangle();
    CRectangle(double length, double height);
    virtual void ShowCharacteristics();
};

If you define a virtual method outside of the class, don't precede its return type with the virtual keyword. When inheriting a class from one that has a virtual method or property, you can indicate that the member is already present on the parent by preceding it with the virtual keyword. Here is an example:

Header File: Box.h
#pragma once

#include "Rectangle.h"

public ref class CBox : public CRectangle
{
private:
    double wdt;

    . . .

public:
    CBox();
    CBox(double Length, double Height, double width);
    virtual void ShowCharacteristics();
};
 

Overriding Virtual Members

If you flag a property or a method in a parent class with the virtual keyword, when deriving a class from it, you can ignore the method or property and not implement it. But if you decide to implement the property or method, you must indicate that you are providing a new version of the property or method. Providing a new version of a property or a method is referred to as overriding it.

When overriding a property or a method, you must indicate this by writing the override keyword to its right. Here are examples:

Header File: Rectangle.h
#pragma once

using namespace System;

public ref class CRectangle
{
protected:
    double len;
    double hgt;

public:
    . . .

    property double Perimeter
    {
	double get() { return 2 * (Length + Height); }
    }

    virtual property double Area
    {
	double get() { return Length * Height; }
    }

public:
    CRectangle();
    CRectangle(double length, double height);
    virtual void ShowCharacteristics();
};
Header File: Box.h
#pragma once

#include "Rectangle.h"

public ref class CBox : public CRectangle
{
private:
    double wdt;

public:
    property double Width
    {
	double get() { return wdt; }

	void set(double w)
	{
	    if( wdt <= 0 )
		wdt = 0;
	    else
		wdt = w;
	}
    }

    virtual property double Area
    {
	double get() override
	{
	    return 2 * ((Length * Height) +
	                (Length * Width) +
			(Height * Width));
	}
    }

    property double Volume
    {
	double get() { return Length * Height * Width; }
    }

public:
    CBox();
    CBox(double Length, double Height, double width);
    virtual void ShowCharacteristics() override;
};
Source File: Exercise.cpp
#include "Rectangle.h"
#include "Box.h"

using namespace System;

int main()
{
    CBox ^ box = gcnew CBox(18.64, 28.42, 40.08);

    CRectangle ^ rect = box;

    rect->ShowCharacteristics();
    Console::WriteLine();
    box->ShowCharacteristics();

    Console::WriteLine();
    return 0;
}
 

A new Implementation of a Parent's Member

There is an alternative to the above technique of overriding a member of a class. In a derived class, if you are implementing a new version of a property or method in a child class and you don't want any confusion with the version in the parent class, you can indicate that the implementation of the member in the derived class is new. To do this, type the new keyword on the right side of the name of the method or in the closing parentheses of the get() or set() methods of the property in the derived class. Here are examples:

using namespace System;

public ref class CCircle
{
private:
    double rad;

public:
    property double Radius
    {
	double get() { return rad; }
	void set(double r)
	{
	    if( r <= 0 )
		rad = 0;
	    else
		rad = r;
	}
    }

    virtual property double Circumference
    {
	double get() { return Radius * 2 * Math::PI; }
    }

    virtual double CalculateArea();
    virtual void Show();
};

double CCircle::CalculateArea()
{
    return Radius * Radius * 3.14159;
}

void CCircle::Show()
{
    Console::WriteLine(L"Circle Characteristics");
    Console::WriteLine(L"Radius:        {0}", Radius);
    Console::WriteLine(L"Circumference: {0}", Circumference);
    Console::WriteLine(L"Area:          {0}", CalculateArea());
}

public ref class CEllipse : public CCircle
{
private:
    double large;

public:
    property double LongRadius
    {
	double get() { return large; }
	void set(double L)
	{
	    if( L <= 0 )
		large = L;
	    else
		large = L;
	}
    }

    virtual property double Circumference
    {
	double get() new { return Radius * LongRadius * Math::PI; }
    }

    virtual double CalculateArea() new;
    virtual void Show() override;
};

double CEllipse::CalculateArea()
{
    return Radius * LongRadius * Math::PI;
}

void CEllipse::Show()
{
    Console::WriteLine(L"Ellipse Characteristics");
    Console::WriteLine(L"Small Radius:  {0}", Radius);
    Console::WriteLine(L"Long Radius:   {0}", LongRadius);
    Console::WriteLine(L"Circumference: {0}", Circumference);
    Console::WriteLine(L"Area:          {0}", CalculateArea());
}

int main()
{
    CCircle ^ circ = gcnew CCircle;

    circ->Radius = 24.75;
    circ->Show();
    Console::WriteLine();

    CEllipse ^ elps = gcnew CEllipse;
    elps->Radius = 15.75;
    elps->LongRadius = 25.25;
    elps->Show();

    Console::WriteLine();
    return 0;
}

This would produce:

Circle Characteristics
Radius:        24.75
Circumference: 155.508836352695
Area:          1924.420224375

Ellipse Characteristics
Small Radius:  15.75
Long Radius:   25.25
Circumference: 1249.37212842449
Area:          1249.37212842449

Press any key to continue . . .

Virtual Destructors

Consider a case where you have created a class that is derived from another class. When you dynamically call the inherited class using an instance of the base class (as done in the last example), during the closing of the program, the destructor of the child class is called first, followed by the destructor of the base class. If you dynamically declare an instance of the base class and then invoke the children of the class, you need to make sure that each destructor and the right destructor of the classes that were used is called to destroy the class. This is a safe measure to avoid memory leak. This aspect of C++ programming is taken care of by declaring the destructor of the base class as virtual.

Whenever the destructor of the parent class is declared virtual, the destructor of an inherited class is also virtual. This ensures that, when the program closes, all of the destructors of the base class and its children that were used are called, providing a safe claim of the memory that was used.

To declare a destructor as virtual, type the virtual keyword on its left, in the body of the class. Here is an example:

public ref class CCircle
{
private:
    double rad;

public:
    property double Radius
    {
	double get() { return rad; }
	void set(double r)
	{
	    if( r <= 0 )
		rad = 0;
	    else
		rad = r;
	}
    }

    virtual property double Circumference
    {
	double get() { return Radius * 2 * Math::PI; }
    }

    virtual double CalculateArea();
    virtual void Show();

    virtual ~CCircle();
};

double CCircle::CalculateArea()
{
    return Radius * Radius * 3.14159;
}

void CCircle::Show()
{
    Console::WriteLine(L"Circle Characteristics");
    Console::WriteLine(L"Radius:        {0}", Radius);
    Console::WriteLine(L"Circumference: {0}", Circumference);
    Console::WriteLine(L"Area:          {0}", CalculateArea());
}

CCircle::~CCircle()
{
}
 

Previous Copyright © 2006 FunctionX, Inc. Next