Chapter 5: Inheritance: Introduction and Rules in Inheritance
Chapter 5: Inheritance: Introduction and Rules in Inheritance
Example
The following C++ code establishes an explicit inheritance relationship between classes
B and A, where B is both a subclass and a subtype of A, and can be used as an A
wherever a B is specified (via a reference, a pointer or the object itself).
class A
{ public:
void DoSomethingALike() const {}
};
class B : public A
{ public:
void DoSomethingBLike() const {}
};
void SomeFunc()
{
B b;
UseAnA(b); // b can be substituted for an A.
}
Types of inheritance
There are various types of inheritance, depending on paradigm and specific language.
Single Inheritance : In the single inheritance, subclasses inherits the features of
a single super class. A class acquire the property of another class.
Multiple Inheritance : Multiple Inheritance allows a class to have more than
one super class and to inherit features from all parent class.
Multilevel Inheritance : In multilevel inheritance a subclass is inherited from
another subclass.
Hierarchical Inheritance : In hierarchical inheritance a single class serves as a
superclass (base class) for more than one sub class.
Hybrid Inheritance : It is a mixture of all the above types of inheritance.
Importance of Inheritance
Inheritance is a good choice when:-
Your inheritance hierarchy represents an "is-a" relationship and not a "has-a"
relationship.
You can reuse code from the base classes.
You need to apply the same class and methods to different data types.
The class hierarchy is reasonably shallow, and other developers are not likely to
add many more levels.
You want to make global changes to derived classes by changing a base class.
1. One of the key benefits of inheritance is to minimize the amount of duplicate code in
an application by sharing common code amongst several subclasses. Where
equivalent code exists in two related classes, the hierarchy can usually be refactored
to move the common code up to a mutual superclass. This also tends to result in a
better organization of code and smaller, simpler compilation units.
2. Inheritance can also make application code more flexible to change because classes
that inherit from a common superclass can be used interchangeably. If the return type
of a method is superclass
3. Reusability -- facility to use public methods of base class without rewriting the same
4. Extensibility -- extending the base class logic as per business logic of the derived class
5. Data hiding -- base class can decide to keep some data private so that it cannot be
altered by the derived class
6. Overriding--With inheritance, we will be able to override the methods of the base class
so that meaningful implementation of the base class method can be designed in the
derived class.
Disadvantages:-
2. Main disadvantage of using inheritance is that the two classes (base and inherited
class) get tightly coupled.
This means one cannot be used independent of each other.
3. Also with time, during maintenance adding new features both base as well as derived
classes are required to be changed. If a method signature is changed then we will be
affected in both cases (inheritance & composition)
4. If a method is deleted in the "super class" or aggregate, then we will have to re-factor
in case of using that method. Here things can get a bit complicated in case of
inheritance because our programs will still compile, but the methods of the subclass
will no longer be overriding superclass methods. These methods will become
independent methods in their own right.
Implementation of Inheritance
Following are attributes observed in the implementation of inheritance
Base & Derived Classes:
A class can be derived from more than one classes, which means it can inherit data and
functions from multiple base classes. To define a derived class, we use a class
derivation list to specify the base class(es). A class derivation list names one or more
base classes and has the form:
class derived-class: access-specifier base-class
Where access-specifier is one of public, protected, or private, and base-class is the
name of a previously defined class. If the access-specifier is not used, then it is private
by default.
Consider a base class Shape and its derived class Rectangle as follows:
#include <iostream>
// Base class
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// Derived class
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
return 0;
}
When the above code is compiled and executed, it produces the following result:
Total area: 35
A derived class inherits all base class methods with the following exceptions:
Constructors, destructors and copy constructors of the base class.
Overloaded operators of the base class.
The friend functions of the base class.
When deriving a class from a base class, the base class may be inherited through
public, protected or private inheritance. The type of inheritance is specified by the
access-specifier as explained above.
We hardly use protected or private inheritance, but public inheritance is commonly
used. While using different type of inheritance, following rules are applied:
Public Inheritance: When deriving a class from a public base class, public
members of the base class become public members of the derived class and
protected members of the base class become protected members of the derived
class. A base class's private members are never accessible directly from a
derived class, but can be accessed through calls to the public and protected
members of the base class.
Protected Inheritance: When deriving from a protected base class, public and
protected members of the base class become protected members of the derived
class.
Private Inheritance: When deriving from a private base class, public and
protected members of the base class become private members of the derived
class.
Concepts in inheritance
Single Inheritance is method in which a derived class has only one base class.
Example:
#include <iostream.h> class Value
{
protected:
int val;
public:
void set_values (int a)
{ val=a;}
}; class Cube: public Value
{
public:
int cube()
{ return (val*val*val); }
}; int main ()
{
Cube cub;
cub.set_values (5);
cout << "The Cube of 5 is::" << cub.cube() << endl;
return 0;
}
Result:
The Cube of 5 is:: 125
Multiple inheritance is achieved whenever more than one class acts as base classes for other
classes. This makes the members of the base classes accessible in the derived class, resulting
in better integration and broader re-usability.
example:
#include <iostream>
using namespace std;
class Cpolygon
{
protected:
int width, height;
public:
void input_values (int one, int two)
{
width=one;
height=two;
}
};
class Cprint
{
public:
void printing (int output);
};
int main ()
{
Crectangle rectangle;
Ctriangle triangle;
rectangle.input_values (2,2);
triangle.input_values (2,2);
rectangle.printing (rectangle.area());
triangle.printing (triangle.area());
return 0;
}
Note:the two public statements in the Crectangle class and Ctriangle class.
A non-member function can access the private and protected members of a class if it is
declared a friend of that class. That is done by including a declaration of this external function
within the class, and preceding it with the keyword friend:
// friend functions
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle() {}
Rectangle (int x, int y) : width(x), height(y) {}
int area() {return width * height;}
friend Rectangle duplicate (const Rectangle&);
};
int main () {
Rectangle foo;
Rectangle bar (2,3);
foo = duplicate (bar);
cout << foo.area() << '\n';
return 0;
}
Friend classes
Similar to friend functions, a friend class is a class whose members have access to the private
or protected members of another class:
// friend class
#include <iostream>
using namespace std;
class Square;
class Rectangle {
int width, height;
public:
int area ()
{return (width * height);}
void convert (Square a);
};
class Square {
friend class Rectangle;
private:
int side;
public:
Square (int a) : side(a) {}
};
int main () {
Rectangle rect;
Square sqr (4);
rect.convert(sqr);
cout << rect.area();
return 0;
}
In this example, class Rectangle is a friend of class Square allowing Rectangle's member
functions to access private and protected members of Square. More concretely, Rectangle
accesses the member variable Square::side, which describes the side of the square.
Pointers to objects
A variable that holds an address value is called a pointer variable or simply pointer.
Pointer can point to objects as well as to simple data types and arrays. sometimes we don’t
know, at the time that we write the program , how many objects we want to creat. when this is
the case we can use new to create objects while the program is running. new returns a pointer
to an unnamed objects. Let’s see the example of student that will clear your idea about this
topic
int main()
{
Simple obj;
Simple* ptr; // Pointer of class type
ptr = &obj;
Object.*pointerToMember
ObjectPointer->*pointerToMember
class Data
{
public:
int a;
void print() { cout << "a is="<< a; }
};
int main()
{
Data d, *dp;
dp = &d; // pointer to object
d.*ptr=10;
d.print();
dp->*ptr=20;
dp->print();
}
The syntax is very tough, hence they are only used under special circumstances.
class Data
{ public:
int f (float) { return 1; }
};
int main(0
{
fp2 = &Data::f; // Assignment inside main()
}
1. You can change the value and behaviour of these pointers on runtime. That
means, you can point it to other member function or member variable.
2. To have pointer to data member and member functions you need to make them
public.
Derived classes do not inherit constructors or destructors from their base classes, but
they do call the constructor and destructor of base classes. Destructors can be declared
with the keyword virtual.
Constructors are also called when local or temporary class objects are created, and
destructors are called when local or temporary objects go out of scope.
Constructor and Destructor invoking sequence with inheritance
Example Program
class Base
{
public:
Base ( )
{
cout << "Inside Base constructor" << endl;
}
~Base ( )
{
cout << "Inside Base destructor" << endl;
}
};
public:
Derived ( )
{
cout << "Inside Derived constructor" << endl;
}
~Derived ( )
{
cout << "Inside Derived destructor" << endl;
}
};
void main( )
{
Derived x;
}
So, here is what the output of the code above would look like:
Inside Base constructor
Inside Derived constructor
Inside Derived destructor
Inside Base destructor
Base class constructors and derived class destructors are called first
In the code above, when the object "x" is created, first the Base class constructor is
called, and after that the Derived class constructor is called. Because the Derived class
inherits from the Base class, both the Base class and Derived class constructors will be
called when a Derived class object is created.
When the main function is finished running, the object x's destructor will get called
first, and after that the Base class destructor will be called.
Base class conversions
Upcasting is converting a derived-class reference or pointer to a base-class. In other
words, upcasting allows us to treat a derived type as though it were its base type. It is
always allowed for public inheritance, without an explicit type cast. This is a result of
the is-a relationship between the base and derived classes.
Here is the code dealing with shapes. We created Shape class, and derived Circle,
Square, and Triangle classes from the Shape class. Then, we made a member function
that talks to the base class:
void play(Shape& s)
{
s.draw();
s.move();
s.shrink();
....
}
The function speaks to any Shape, so it is independent of the specific type of object
that it's drawing, moving, and shrinking. If in some other part of the program we use
the play( ) function like below:
Circle c;
Triangle t;
Square sq;
play(c);
play(t);
play(sq);
Let's check what's happening here. A Triangle is being passed into a function that is
expecting a Shape. Since a Triangle is a Shape, it can be treated as one by play(). That
is, any message that play() can send to a Shape a Triangle can accept.
Upcasting allows us to treat a derived type as though it were its base type. That's how
we decouple ourselves from knowing about the exact type we are dealing with.
Note that it doesn't say "If you're a Triangle, do this, if you're a Circle, do that, and so
on." If we write that kind of code, which checks for all the possible types of a Shape, it
will soon become a messy code, and we need to change it every time we add a new
kind of Shape. Here, however, we just say "You're a Shape, I know you can move(),
draw(), and shrink( ) yourself, do it, and take care of the details correctly."
The compiler and runtime linker handle the details. If a member function is virtual, then
when we send a message to an object, the object will do the right thing, even when
upcasting is involved.
Note that the most important aspect of inheritance is not that it provides member
functions for the new class, however. It's the relationship expressed between the new
class and the base class. This relationship can be summarized by saying, "The new
class is a type of the existing class."
class Parent {
public:
void sleep() {}
};
int main( )
{
Parent parent;
Child child;
return 0;
}
A Child object is a Parent object in that it inherits all the data members and member
functions of a Parent object. So, anything that we can do to a Parent object, we can do
to a Child object. Therefore, a function designed to handle a Parent pointer (reference)
can perform the same acts on a Child object without any problems. The same idea
applies if we pass a pointer to an object as a function argument. Upcasting is transitive:
if we derive a Child class from Parent, then Parent pointer (reference) can refer to a
Parent or a Child object.
Upcasting can cause object slicing when a derived class object is passed by value as a
base class object, as in foo(Base derived_obj).
Downcasting
The opposite process, converting a base-class pointer (reference) to a derived-class
pointer (reference) is called downcasting. Downcasting is not allowed without an
explicit type cast. The reason for this restriction is that the is-a relationship is not, in
most of the cases, symmetric. A derived class could add new data members, and the
class member functions that used these data members wouldn't apply to the base class.
As in the example, we derived Child class from a Parent class, adding a member
function, gotoSchool(). It wouldn't make sense to apply the gotoSchool() method to a
Parent object. However, if implicit downcasting were allowed, we could accidentally
assign the address of a Parent object to a pointer-to-Child
Child *pChild = &parent; // actually this won't compile
// error: cannot convert from 'Parent *' to 'Child *'
and use the pointer to invoke the gotoSchool() method as in the following line.
pChild -> gotoSchool();
Because a Parent isn't a Child (a Parent need not have a gotoSchool() method), the
downcasting in the above line can lead to an unsafe operation.
C++ provides a special explicit cast called dynamic_cast that performs this conversion.
Downcasting is the opposite of the basic object-oriented rule, which states objects of a
derived class, can always be assigned to variables of a base class.
Dynamic Casting
The dynamic_cast operator answers the question of whether we can safely assign the
address of an object to a pointer of a particular type.
Here is a similar example to the previous one.
#include <string>
class Parent {
public:
void sleep() {
}
};
int main( )
{
Parent *pParent = new Parent;
Parent *pChild = new Child;
It returns 0, otherwise.
Notes
downcast - A downcast is a cast from a base class to a class derived from that base
class.
cross-cast - A cross-cast is a cast between unrelated types (user-defined conversion)
Class Scope under Inheritance
Each class defines its own scope within which its members are defined. Under
inheritance, the scope of a derived class is nested inside the scope of its base classes. If
a name is unresolved within the scope of the derived class, the enclosing base-class
scopes are searched for a definition of that name.
The fact that the scope of a derived class nests inside the scope of its base classes can
be surprising. After all, the base and derived classes are defined in separate parts of our
program‘s text. However, it is this hierarchical nesting of class scopes that allows the
members ...
Overloading with Inheritance
Overloading doesn‘t work for derived class in C++ programming language. There is no
overload resolution between Base and Derived. The compiler looks into the scope of
Derived, finds the single function ―double f(double)‖ and calls it. It never disturbs with
the (enclosing) scope of Base. In C++, there is no overloading across scopes – derived
class scopes are not an exception to this general rule.
Inheritance relationship
Subclasses and superclasses can be understood in terms of the is a relationship. A subclass is a
more specific instance of a superclass. For example, an orange is a citrus fruit, which is a fruit.
A shepherd is a dog, which is an animal.
If the is a relationship does not exist between a subclass and superclass, you should not use
inheritance.
Note: inheritance only reuses implementation and establishes a syntactic relationship, not
necessarily a semantic relationship (inheritance does not ensure behavioral subtyping). To
distinguish these concepts, subtyping is also known as interface inheritance, while inheritance
as defined here is known as implementation inheritance.