Topic 9: C++ Inheritance
Synopsis
Inheritance in C++
-
Recall that inheritance is a means of specifying hierarchical relationships
between types
-
C++ classes¤
can inherit both data and function members¤¤
from other (parent) classes¤
-
In addition, they inherit the "is a" relationship so that an object of
a class¤
SortedList (which inherits from class¤
List) can be treated as an object of class¤
List in most cases
-
Terminology: "the child (or derived) class¤
inherits (or is derived from) the parent (or base)
class¤."
An example
class Name
{
public:
Name(void) { myName = 0; }
~Name(void) { delete[] myName; }
void SetName(char* n)
{
myName = new char[strlen(n)+1];
strcpy(myName,n);
}
void Print(void) { cout << myName << endl; }
private:
char* myName;
};
class Contact: public Name
{
public:
Contact(void) { myAddress = 0; }
~Contact(void) { delete[] myAddress; }
void SetAddress(char* c)
{
myAddress = new char[strlen(c)+1];
strcpy(myAddress,c);
}
void Print(void)
{
Name::Print();
cout << myAddress << endl;
}
private:
char* myAddress;
};
int main(void)
{
Contact c;
c.SetName("John McClane");
c.SetAddress("137th floor, Nakatome Towers");
c.Print();
}
What a derived class¤¤
inherits
-
Every data member¤¤
defined in the parent class¤¤
(although such members¤
may not always be accessible in the derived class¤¤!)
-
Every ordinary member¤
function¤
of the parent class¤¤
(although such members¤
may not always be accessible in the derived class¤¤!)
-
The same initial data layout as the base class¤¤
What a derived class¤¤
doesn't inherit
What
a derived class¤¤
can add
What
happens when a derived-class¤
object is created and destroyed
-
Space is allocated (on the stack or the heap) for the full object (that
is, enough space to store the data members¤¤
inherited from the base class¤¤
plus the data members¤¤
defined in the derived class¤¤
itself)
-
The base class¤¤'s
constructor¤
is called to initialize the data members¤¤
inherited from the base class¤¤
-
The derived class¤¤'s
constructor¤
is then called to initialize the data members¤¤
added in the derived class¤¤
-
The derived-class¤
object is then usable
-
When the object is destroyed (goes out of scope or is deleted) the derived
class¤¤'s
destructor¤
is called on the object first
-
Then the base class¤¤'s
destructor¤
is called on the object
-
Finally the allocated space for the full object is reclaimed
Public
vs private inheritance¤
-
The public keyword in the inheritance syntax means that publicly accessible
members¤
inherited from the base class¤¤
stay publicly accessible in the derived class¤¤
(e.g. the public Name::SetName(char*) was inherited as
the public Contact::SetName(char*)).
-
But sometimes its preferable to inherit the public members¤
of a parent in such a way that they become private in the child
-
We can do this by using the private keyword in the inheritance specification:
class Stack : private List
{
public:
void Push(int i)
{ First(); Insert(i); }
int Pop(void)
{
First();
int top = GetCurrent();
DeleteCurrent();
return top;
}
};
int main(void)
{
Stack s;
s.Push(1)
s.Push(2);
cout << s.Pop() << endl;
s.Next(); // ERROR: List::Next() PRIVATELY INHERITED!
}
The relationship between a parent and child class¤¤
under private inheritance¤
is not "is a", but "is implemented as a"
Another example
class Setter
{
public:
void Set(char*& variable, char* value)
{
variable = new char[strlen(value)+1];
strcpy(variable,value);
}
};
class Name: private Setter
{
public:
Name(void) { myName = 0; }
~Name(void) { delete[] myName; }
void SetName(char* n)
{ Set(myName,n); }
void Print(void)
{ cout << myName << endl; }
private:
char* myName;
};
class Contact: public Name
{
public:
Contact(void) { myAddress = 0; }
~Contact(void) { delete[] myAddress; }
void SetAddress(char* c)
{ Set(myAddress,c); } // ERROR! WHY?
void Print(void)
{
Name::Print();
cout << myAddress << endl;
}
private:
char* myAddress;
};
int main(void)
{
Contact c;
c.SetName("John McClane");
c.SetAddress("137th floor, Nakatome Towers");
c.Print();
}
Protected access
-
Recall that private members¤
of the base class¤¤
are not accessible in the derived class¤¤
(to preserve encapsulation¤)
-
Sometimes, however, we would like to be able to define encapsulated data
members¤¤
which are not publicly accessible, but which are accessible to derived
classes¤¤:
class Coefficient
{
public:
Coefficient(void)
{ myValue = 0; myAccesses = 0; }
double GetValue(void) const
{ myAccesses++; return myValue; }
bool SetValue(double v)
{
myAccesses++;
if (v<0 || v>1) { return false; }
myValue = v;
return true;
}
private:
double myValue;
mutable int myAccesses;
};
enum Status { good, bad, ugly };
class StatusCoefficient : public Coefficient
{
public:
StatusCoefficient(void)
{ myStatus = ugly; }
Status GetStatus(void) const
{
myAccesses++; // ACCESS ERROR!
return myStatus;
}
bool SetStatus(Status s)
{
myAccesses++; // ACCESS ERROR!
myStatus = s;
return true;
}
private:
int myStatus;
};
So C++ provides a third access specifier: protected:
"Protected" members¤
are not accessible from outside the class¤,
except in derived classes¤¤
So a protected member¤
which is inherited, is accessible in the derived class¤¤
(and remains protected there):
class Coefficient
{
public:
Coefficient(void)
{ myValue = 0; myAccesses = 0; }
double GetValue(void) const
{ myAccesses++; return myValue; }
bool SetValue(double v)
{
myAccesses++;
if (v<0 || v>1) { return false; }
myValue = v;
return true;
}
private:
double myValue;
protected:
mutable int myAccesses;
};
enum Status { good, bad, ugly };
class StatusCoefficient : public Coefficient
{
public:
StatusCoefficient(void)
{ myStatus = ugly; }
Status GetStatus(void) const
{
myAccesses++; // ACCESS OKAY!
return myStatus;
}
bool SetStatus(Status s)
{
myAccesses++; // ACCESS OKAY!
myStatus = s;
return true;
}
private:
int myStatus;
};
Initializer
lists¤
-
Recall that the derived class¤¤
constructor¤
automatically calls the "no argument" base class¤¤
constructor¤(s)
-
So, for example, StatusCoefficient::StatusCoefficient(void)
automatically calls Coefficient::Coefficient(void)
-
But what if a base class¤¤
has a non-void constructor¤
which needs to be called?
-
In C++ we can specify which base-class¤
constructor¤
gets called, as part of the definition of the derived-class¤
constructor¤:
class Coefficient
{
public:
Coefficient(void)
{ myValue = 0; myAccesses = 0; }
Coefficient(double initval)
{ myValue = initval; myAccesses = 0; }
// ETC. AS BEFORE...
};
class StatusCoefficient : public Coefficient
{
public:
StatusCoefficient(void)
{ myStatus = ugly; }
StatusCoefficient(double initval, Status initStatus)
: Coefficient(initval)
{ myStatus = initStatus; }
// ETC. AS BEFORE...
};
In this version, the StatusCoefficient::StatusCoefficient(void)
constructor¤
still automatically calls Coefficient::Coefficient(void),
but the StatusCoefficient::StatusCoefficient(double,Status)
constructor¤
is explicitly specified to automatically call Coefficient::Coefficient(double).
This explicit specification is called an initializer list¤
Initializer lists¤
can also be used to initialize class¤
members¤
with specific value (instead of assigning to them in the body of the constructor¤):
class Coefficient
{
public:
Coefficient(void)
: myValue(0), myAccesses(0)
{}
Coefficient(double initval)
{ myValue = initval; myAccesses = 0; }
// ETC. AS BEFORE...
};
class StatusCoefficient : public Coefficient
{
public:
StatusCoefficient(void)
: myStatus(ugly)
{}
StatusCoefficient(double initval, Status initStatus)
: Coefficient(initval), myStatus(initStatus)
{}
// ETC. AS BEFORE...
};
This is more efficient too. Why?
Reading
- Lippman & Lajoie
- New: Chapter 17.
- Stroustrup
- New: Chapter 12.
This material is part of the CSE2305
- Object-Oriented Software Engineering course.
Copyright © Jon McCormack & Damian Conway, 1998–2005. All rights
reserved.