C++ Notes
Home Up C Notes C++ Notes Data structure Notes

 

Excerpts from C++ Notes

...

 

An abstract base class

 

 

An abstract base class cannot be instantiated; it is as described, abstract, it exists only in theory.

 

The purpose of such a beast is to provide a template or shell description of objects that must follow a certain guideline.

 

In a nutshell, as designers of the linked list data structure, we are going to say:

 

 

Dear application designer:

 

            If you wish to add objects to my data structure, you must derive from my abstract base class and I'm going to force you to code an comparison function otherwise your class won't compile.

 

 

In general, an abstract base class can define any number of data or functional members, but functional members that are not defined are labelled as null via an = 0 declaration:

 

 

            class AbstractBaseClass

            {

                        public:

                                 // The deriver promises to define f1()

                                    void f1() = 0;

            };

 

  

 

 

                            -------------------

                           |   X                   |

                           |                         |

                           |   void f1() {...} |

                            -------------------

                                  /

       - - - - - - - - -    /

      |AbstractBaseClass |

      |                              |

       - - - - - - - - - - - - - -

                            \

                             \

                           ---------------------

                          |   Y                     |

                          |                           |

                          |   void f1() {...}   |

                           ---------------------


 

An abstract object definition

 

 

 

 

class Object

{

               public:

                              virtual int compare(const Object&) = 0;                        

};

 

 

 

Suppose a user wants to populate our data structure with InventoryRecords.  Then the user derives from Object and provides a compare function describing how to compare InventoryRecords.

 

 

 

 

class InventoryRecord : public Object

{

               public:

                              ...

                              int compare(const Object& o) {...}      

};

 

           

 

 

 

 - - - - - -            ----------------------

| Object     |   <---  |  InventoryRecord     |

|                  |         |    int compare(...)  |

 - - - - - -            ----------------------

 

...

 

A musical instrument derivation hierachy

 

 

 

 

 

             -----------------------------------

            |     Musical Instrument            |

             -----------------------------------

            /                    |             \     

Woodwind                 Brass           String

 |       \               /    \           /  \ 

 |        \             /      \         /    \

Clarinet  Saxophone  Trumpet Tuba   Guitar   Violin

                                    /      \

                                   /        \

                                Electric  Acoustic

                                   |          |

                                 Ibanez      Samick

 

 


...

 

Multiple inheritance

 

 

In more complex programs, we may wish to derive a class from more than one counterpart.  An AND condition describes multiple inheritance. 

 

 

Theoretically:

 

 

     ----   ---

    | X  | | Y |

     ----   ---

       \    /         Z is an X and a Y

        \  /

       ------

      |  Z   |

       ------

 

 

Practically:

 

 

    ---------     ---------           ----------

   | Student |   | Person  |         |  Person  |

    ---------     ---------           ----------

        \          /           OR         |

         \        /                       |

      -------------------             -----------

     | UniversityStudent |           | Student   |

      -------------------             -----------

                                          |

                                          |

                                     -------------------

                                    | UniversityStudent |

                                     -------------------

 

 

In both cases,  UniversityStudent is a Student and a Person

 

Actually, the second derivation chart may be a more realistic model since we have more concise definition of Student as Student being a Person.

 

Regardless, they are both examples of multiple inheritance.


 

Memory map of multiple inheritance

 

 

To create an AND derived relation in C++, we simple append the extra class(es) after the colon operator separated by commas.  We could code the first model as:

 

 

class UniversityStudent : public Student,Person

{ ... };

 

 

 

Suppose a Person has a birthDate and a countryOfBirth.  Then creating an object u of type UniversityStudent yields something like the following in memory:

 

 

 

   

                    -------------------

"Person"   |  birthDate            |

 part          |  countryOfBirth   |

                 | - - - - - - - - -         |

                 |  name                  |

"Student"  |  marks[]              |

 part          |                            |

                    -------------------

 

 

 

All data and functional members of Student and Person are available at the UniversityStudent level.  We can write:

 

 

 

UniversityStudent u(...);

cout << u.getName() << u.average() << u.getBirthdate() << endl;

 

 

 

 

* Note if we opt for this design, we may decide to move the name field from the Student class to the Person class since it makes more sense group-wise to say that every Person has a name, birthdate and countryOfBirth.


 

Inheritance versus embedding

 

 

Another way to achieve the same results is to embed instances of our derived classes inside the subclass.

 

 

 

class UniversityStudent

{

               public:

                              Student s;   // Here, we are saying that a UniversityStudent

                              Person  p;   // has Student and Person counterparts

               ...

};

 

 

This works but the syntax is clumsier.  Suppose we want to access the Student or Person components of u, a UniversityStudent object,

 

 

UniversityStudent u(..);

cout << u.s.getName() << u.p.getBirthdate() << endl;

 

 

 

IS A is a stronger relation than HAS A, so if we recognize an IS A relation, we should not kludge it with HAS A condition(s).

 

...

 

Exercises

 

(3) Design a Dstring class which provides dynamic string management.  A dynamic string can grow or shrink through the operations provided on the class.

 

           

 Dstring s1("abc"),s2("def),s3(s1),s4;

 

 s3 = s1 + s2      : concatenates s1 to s2 and returns a result

 s3 = s1.mid(4,6); : extracts 6 characters from s1 starting from the 5th character

 s3 = s1.left(4);  : extracts the leftmost 4 characters of s1

 s3 = s1.right(2); : extracts the rightmost 2 characters of s3

 cout << s1[2];    : extracts the 3rd character of sd

 s4 = s2 = s1      : assigns s4 = s1 and s3 = s2

 

   

. You will need to a code constructor(s), a copy constructor, an assignment operator, and a destructor.

. Allow complex numbers to be piped to/from standard input

  and file streams

. Test your class

 

...

 

(5) The game of chess involves pieces of various types that have

various movement patterns.  The pieces are

{PAWN,KNIGHT,BISHOP,KING,ROOK, QUEEN}.  All these pieces can be

derived from an abstract class called Piece.  Each piece moves

differently and can be queried for its squares of movement by

calling the function getMoves().  Here is a sample board:

 

        ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑

     8 | BR BN   BK                       |  W = white, B = Black

     7 | BP            BB BB               |  K = king, P = pawn, B = bishop

     6 |            BP                           |  N = knight, R = Rook, Q= queen

     5 |    BP   BR       BP               |

     4 |                  WP BP              |  For ex, WN at Position(B,3)

     3 |    WN   WR WP WP    WP |  has the following squares to

     2 |       WB                               |  move to:

     1 |    WK       WQ                    |  (A,1),(C,1),(D,4),(D,2),(C,5)

        ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑              (A,5)

         A  B  C  D  E  F  G  H

 

* Note that a queen's movement can be monitored by sending a

getMoves() message to a Bishop object and a Rook object of the

same color at the same square.

 

Class diagram:

 

     Piece          <-- Rook

     |-name                Position[] getMoves()

     |-position     <-- Bishop

     |-colour               Position[] getMoves()

                     ...

 

     Position

     |‑row

     |‑column

 

     Color: [WHITE,BLACK]

 

     Board

     |-8x8 array of pieces

 

Instantiate a board and populate it with the above sample setup.

Display the board, then let the user ask where any piece on the

board can be moved to:

 

Sample run:

 

Enter piece position => B3

White knight at B3 can move to {A1, C1, D4, D2, C5, A5}

Enter piece position => H1

?? No piece at H1

Enter piece position =>