To illustrate the idea of inheritance, let's assume that we need to develop a project that should work with students, graduate students, professors, and alumni. We need to keep slightly different set of data for a member each of these groups, but all of them should have:
class Person {
private:
String name;
String address;
String phone;
String ID;
int SSN;
public:
Person();
~Person();
void PrintName();
...
};
And make this class to be the base class for all other classes.
class new_class : [access] base_class {
...
};
where access is one of the following keywords:
class Student : public Person {
private:
Course courses_taken[];
String major;
String minor;
protected:
bool keep_privately;
public:
Student();
~Student();
float GPA();
...
};
class GradStudent : public Student {
private:
Course grad_courses[];
Professor *advisor;
public:
GradStudent();
~GradStudent();
void PrintAdvisor();
...
};
If we defined the classes Person, Student, and GradStudent as shown, the we say that
A derived class inherits all public and protected members from its base class excluding:
GradStudent stud; stud.PrintName(); cout<<stud.GPA();Please note that:
UML uses the following notation for derived classes:
class Name {
public:
declaration of public (visible) members
protected:
declaration of protected members
private:
declaration of private members
};
We know that public members are visible from outside the class and that private members are visible only from inside
and for friends of the class. In the absence of inheritance the protected members behave exactly like private members;
that is, they are not visible outside. However, there is a difference between private and protected
members if there is a class derived from the class under consideration. Namely, programmers can access the protected
members of the base class inside the derived class.
For example, if we have the BaseClass class defined as
class BaseClass {
public:
int number;
protected:
char letter;
private:
bool test;
};
and the class DerivedClass defined as
class DerivedClass : public BaseClass {
public:
int another_number;
void Print();
};
then inside the method Print() we can access the following variables:
DerivedClass object; object.number = 10; // OK - it's public object.another_number = 0; // OK - it's public
However, if we change the access mode in the definition of the derived class from public to protected:
class DerivedClass : protected BaseClass {
public:
int another_number;
void Print();
};
then data member number inherited by the derived class is no longer public, it is protected and cannot be
accessed from outside the class:
DerivedClass object; object.another_number = 0; // OK - it's public object.number = 10; // ERROR - it's protectedIf we have a class derived from the class DerivedClass, though. This new class will be able to access this variable number.
#include <iostream>
using namespace std;
class BaseClass {
public: BaseClass() { cout<<"Base class constructor\n"; };
};
class DerivedClass : public BaseClass {
public: DerivedClass() { cout<<"Derived class constructor\n"; };
};
void main()
{
DerivedClass object;
}
the output would be
Base class constructor Derived class constructor
If the base class has other constructors besides the default one and we would like to use any of them instead of automatically invoked default one, we can do so by specifying the exact constructor (by passing it the right arguments) in the initialization list. For example, if in the previous code the BaseClass also has a constructor like:
BaseClass(string word) { cout<<word<<" ==> Base class constructor\n"; };
then this constructor can be explicitly invoked in the DerivedClass constructor by
DerivedClass(string word) : BaseClass(word) { cout<<word<<" ==> Derived class constructor\n"; };
If we now declare an object of the DerivedClass initialized with the new constructor, then the new constructor
of the derived class will explicitly call the alternative constructor of the BaseClass. For example, the line
DerivedClass obj("Alternative");
in the main function will generate the following output:
Alternative Base class constructor Alternative Derived class constructorYou can fine the complete source code of the example on the example page.
If the base class has constructors but no default constructor, then a derived class constructor must explicitly call some of the base class constructors. Please try to comment out the default constructor in the previous example and compile the program. You will end up having the following error message:
derived_constr.cpp(21) : error C2512: 'BaseClass' : no appropriate default constructor availableThis is another reason to recommend always have a default constructor in a class. However, you won't get any error if the base class has no constructors at all.
The following summarizes the rules for invoking the constructors (see [1] for details):
As you can guess, if we have a class C indirectly derived from a class A through a class B and we declare an object of the C type, then the constructor of B will be implicitly invoked before the C constructor, and the A constructor will be implicitly invoked before the B constructor starts. In other words, the constructors will be invoked accordingly the inheritance hierarchy from the base class to the derived class.
Situations with the destructors is a little bit easier. First of all, the destructors are executed in the order opposite to the inheritance hierarchy; that is, from the derived class to the base class. In other words, if an object of a derived class gets destroyed, then first the destructor of the derived class (is there is any) will be invoked, and then the destructor of the base class will be automatically called (again, if there is any). For example, the following code
#include <iostream>
using namespace std;
class BaseClass {
public:
~BaseClass() { cout<<"Base class destructor\n"; };
};
class DerivedClass : public BaseClass {
public:
~DerivedClass() { cout<<"Derived class destructor\n"; };
};
void main()
{
DerivedClass object;
}
will generate the output:
Derived class destructor Base class destructorUnlike constructors, a destructor never explicitly invokes another destructor.