2-5-1) Single Inheritance
 

Revisions:
Nov 7, 2007: added section on combining static and dynamic binding



C++ Provides single and multiple inheritance, here we will look a some of the details of single inheritance including, abstract classes, public, private and protected inheritance.

Virtual Functions

C++ uses Static binding by default, dynamic binding is activiated by defining methods as virtual.



#include <iostream.h>
//Static Binding Example
class BankAccount{
protected:
 float balance;
 int accountNumber;
public:
 BankAccount(int acctnum) : accountNumber(acctnum), balance(0) {}
 BankAccount & deposit(float amount) {balance += amount; return *this;}
 void printOn(ostream & o) {o << "BankAccount #" << accountNumber << "\n";}
};
ostream & operator<<(ostream & o, BankAccount & b) {
 b.printOn(o); return o;
}

class SavingAccount : public BankAccount {
public:
 SavingAccount(int acctnum) : BankAccount(acctnum) {}
 void printOn(ostream & o) {o << "SavingAcct #" << accountNumber << "\n";}
};

void main() {
 BankAccount b(1000);
 SavingAccount s(1001);
 cout << b << s;
}

BankAccount #1000
BankAccount #1001
 



#include <iostream.h>
//Dynamic Binding Example --uses virtual function
class BankAccount{
protected:
 float balance;
 int accountNumber;
public:
 BankAccount(int acctnum) : accountNumber(acctnum), balance(0) {}
 BankAccount & deposit(float amount) {balance += amount; return *this;}
 virtual void printOn(ostream & o) {o << "BankAccount #" << accountNumber << "\n";}
};
ostream & operator<<(ostream & o, BankAccount & b) {
 b.printOn(o); return o;
}

class SavingAccount : public BankAccount {
public:
 SavingAccount(int acctnum) : BankAccount(acctnum) {}
 void printOn(ostream & o) {o << "SavingAcct #" << accountNumber << "\n";}
};

void main() {
 BankAccount b(1000);
 SavingAccount s(1001);
 cout << b << s;
}

BankAccount #1000
SavingAcct #1001
 
 



Pure Virtual Functions

C++ uses pure virtual functions to define abstract classes. You cannot create an object from an abstract class, only concrete subclass objects can be created. The subclasses must implement the pure virtual function.



#include <iostream.h>
//Dynamic Binding Example, pure virtual functions
class BankAccount{
protected:
 float balance;
 int accountNumber;
public:
 BankAccount(int acctnum) : accountNumber(acctnum), balance(0) {}
 virtual BankAccount & deposit(float amount) = 0; //pure virtual function
 void printOn(ostream & o) {o << "BankAccount #" << accountNumber << "\n";}
};
ostream & operator<<(ostream & o, BankAccount & b) {
 b.printOn(o); return o;
}

class SavingAccount : public BankAccount {
public:
 SavingAccount(int acctnum) : BankAccount(acctnum) {}
 BankAccount & deposit(float amount) {balance += amount;}
 void printOn(ostream & o) {o << "SavingAcct #" << accountNumber << "\n";}
};

void main() {
 // BankAccount b(1000); //ERROR: NOT LEGAL BankAccount is an abstract class
 SavingAccount s(1001);
 s.deposit(500.0);
 cout << s;
}

SavingAccount #1001
 
 


Combining Static and Dynamic Binding 

Because static binding is faster than dynamic binding, C++ code sometimes combines the two. The dynamically bound methods are used to define the public protocol of classes, but statically bound methods are used to do private work. This allows both extendable decoupling and avoids overhead in private work.

 Sample Code

/*

This example shows static and dynamic binding being combined. The BankAccount class defines
a public, and virtual, protocol for doing deposits and withdrawls. The subclasses are expected
to implement this protocol and apply their service charges. The subclasses will then use the
statically bound basicDeposit and basicWithdraw methods of the BankAccount class to make the
actual changes to the account balance.

*/

class BankAccount{
// ===========
private:
     double balance;
     int accountNumber;
public:

BankAccount(int acctnum) : accountNumber(acctnum), balance(0) {}

//dynamically bound public protocol
virtual BankAccount & deposit(double anAmount)=0;
virtual BankAccount & withdraw(double anAmount)=0;
virtual void printOn(ostream & o) {o << "Account #" << accountNumber << " $" << balance;}

//statically bound protected methods
protected:
BankAccount & basicDeposit(double amount) {
   balance += amount;
   return *this;
}

BankAccount & basicWithdraw(double amount) {
   balance -= amount;
   return *this;
}

};

ostream & operator<<(ostream & o, BankAccount & b) {
     b.printOn(o); return o;
}

class SavingAccount : public BankAccount {
// ==================================
static double serviceChargeRate;
public:
SavingAccount(int acctnum) : BankAccount(acctnum) {}

BankAccount & deposit(double anAmount){
     basicDeposit(anAmount);
     basicWithdraw(anAmount * serviceChargeRate);
     return *this;
}

BankAccount & withdraw(double anAmount){
     basicWithdraw(anAmount);
     basicWithdraw(anAmount * serviceChargeRate);
     return *this;
}

void printOn(ostream & o) {
     o << "Saving ";
     BankAccount::printOn(o);
     o << "\n";
}

};

double SavingAccount::serviceChargeRate = 0.05;
 


#include <iostream>
using namespace std;
#include "bankaccount.h"

void main() {
     SavingAccount s1(1000);
     SavingAccount s2(1001);
     s1.deposit(1000.00).withdraw(300);
     cout << s1 << s2;
}

 


Public  Inheritance and the "Is a" relationship

public inheritance is approriate for an "Is a" relationship. A subclass object behaves like a superclass object, because it is a superclass object as well. For example, a SavingAccount is a BankAccount, so if you can deposit into a BankAccount, you should be able to deposit into a SavingAccount.



 

#include <iostream>

using namespace std;

class BankAccount{
protected:
  
float balance;
  
int accountNumber;

public:
   BankAccount(
int acctnum) : accountNumber(acctnum), balance(0) {}
  
float getBalance(void) {return balance; }
  
virtual BankAccount & deposit(float amount) {balance += amount; return *this;}
  
virtual void printOn(ostream & o) {o << "BankAccount #"
<< accountNumber;}
};

ostream & operator<<(ostream & o, BankAccount & b) {
   b.printOn(o);
return
o;
}

class SavingAccount : public BankAccount {
public:
   SavingAccount(
int acctnum) : BankAccount(acctnum) {}
  
void printOn(ostream & o) {o << "SavingAcct #"
<< accountNumber;}
};

void main() {
   SavingAccount s(1001);
   s.deposit(500.0);
   cout << s <<
" $"
<< s.getBalance();
}

SavingAccount #1001 $500.0

 


Destruction -Why Destructors need to be virtual

#include <iostream>
using namespace std;

class A{
public:
    A() {cout << "A()\n";}

   ~A() {cout << "~A()\n";}  //should be virtual
};

class B : public A {

public:
   B() {cout << "B()\n";}

   ~B() {cout << "~B()\n";}
};

void main() {
A * ap;
ap = new B;
delete ap;
}

/*output
A()
B()
~A()  ---destroyed as an A instance not B instance
      ---solution: make the destructors virtual.
*/

 



Private  Inheritance and the "Part Of" relationship

Private inheritance is approriate for an "Part of" relationship. A subclass object does not publicly behave like a superclass object, because "is not" superclass object as well. Instead the superclass object is helping to add private behaviour. For example, a Stack might use Vector code, but a Stack should not behave like a Vector. Do not confuse private inheritance with true aggregation. With true aggregation a Stack would actually contain a Vector object.



//Aggregation Example
typedef int T;
class Vector{
    T * buffer;
public:
 Vector(void) : buffer(new T[100]) {}
 ~Vector(void) {delete [] buffer;}
 T& operator[](int index) {return buffer[index]; }

};

class Stack  {
 Vector v; //Stack contains a Vector Object
 int top;
public:
 Stack(void) : top(0) {}
    Stack & push(T item) {v[top++] = item; return *this;}
 T pop(){return v[--top] ;}
};

void main() {

Stack s;
s.push(5).push(7);
cout << s.pop();
//cout << s[1]; NOT LEGAL
}



Using private inheritance instead of aggregation. Notice further subclassing may not be possible with private inheritance.

typedef int T;
class Vector{
    T * buffer;
public:
 Vector(void) : buffer(new T[100]) {}
 ~Vector(void) {delete [] buffer;}
 T& operator[](int index) {return buffer[index]; }

};

class Stack : private Vector {
protected:
 int top;
public:
 Stack(void) : top(0) {}
    Stack & push(T item) {operator[](top++) = item; return *this;}
 T pop(){return operator[](--top);}
};

/*
//NOT LEGAL with private inheritance
//BoundedStack cannot "see" operator[]
class BoundedStack : public Stack{
 int bound;
public:
  BoundedStack(int b) : bound(b) {}
  Stack & push(T item) {
   if(top < bound) operator[](top++) = item; return *this;}
};
*/

void main() {

Stack s;
s.push(5).push(7);
cout << s.pop();
//cout << s[1]; NOT LEGAL, the operator[] is private to Stack
}



Using protected  inheritance allows further subclassing.

typedef int T;
class Vector{
    T * buffer;
public:
 Vector(void) : buffer(new T[100]) {}
 ~Vector(void) {delete [] buffer;}
 T& operator[](int index) {return buffer[index]; }

};

class Stack : protected Vector {
protected:
 int top;
public:
 Stack(void) : top(0) {}
    Stack & push(T item) {operator[](top++) = item; return *this;}
 T pop(){return operator[](--top);}
};
 

//LEGAL with protected inheritance
//BoundedStack can "see" operator[]
class BoundedStack : public Stack{
 int bound;
public:
  BoundedStack(int b) : bound(b) {}
  Stack & push(T item) {
   if(top < bound) operator[](top++) = item; return *this;}
};
 

void main() {

BoundedStack s(50);
s.push(5).push(7);
cout << s.pop();
//cout << s[1]; NOT LEGAL, the operator[] is private to Stack
}



How Virtual Function Are Implemented

The following slides illustrate how virtual functions are implemented in C++