Inheritance is a fundamental concept in object-oriented programming that allows you to create new classes based on existing ones. It enables code reuse and provides a mechanism to represent real-world relationships between entities. In C++, inheritance allows a class (the derived class) to inherit properties and behaviors (data members and member functions) from another class (the base class).
§1 Inheritance #
Inheritance is an object-oriented programming concept, it is a mechanism that allows one class (the derived class) to reuse and extend the state and behavior of another class (the base class).
There are three related concepts you must always keep in mind:
- Base Class: The class whose properties are inherited, sometimes referred to as the “parent” class.
- Derived Class: The class that inherits the properties of the base class, also known as the “child” class.
- Access Specifiers: Control the accessibility of inherited members.
- public: Public members remain
publicin the derived class. - private: Private members are not accessible in the derived class.
- protected: Protected members remain
protectedin the derived class.- A
protectedmember is accessible to derived classes, but is otherwise treated asprivate.
- A
- public: Public members remain
Syntax:
// a base class
class MyBase {
public:
int baseValue;
void baseFunction() {
std::cout << "Base class function" << std::endl;
}
};
// a derived class
// uses syntax: `derived_class_name : access_specifier base_class_name`
class MyDerived : public MyBase {
public:
void derivedFunction() {
std::cout << "Derived class function" << std::endl;
}
};
In this example, the MyDerived class inherits from the MyBase class. MyDerived has access to baseValue and baseFunction() from the MyBase class, while it also has its own member function derivedFunction().
Inherited classes will automatically have access to all public and protected members of their base (or parent) class.
// base class
class MyBase {
public:
int baseValue;
void baseFunction() {
std::cout << "Base class function" << std::endl;
}
protected:
double protectedValue;
private:
long privateValue;
};
// derived class
class MyDerived : public MyBase {
public:
void derivedFunction() {
std::cout << "Derived class function" << std::endl;
// protected members are accessible in derived classes
std::cout << protectedValue << std::endl;
// invalid, private values can never be accessed outside of the owning class
//std::cout << privateValue << std::endl;
}
};
int main() {
// Create an object of the base class
MyBase baseObj;
baseObj.baseFunction(); // Calls base class method
// Create an object of the Derived class
MyDerived derivedObj;
derivedObj.baseFunction(); // Calls inherited Base class method
//derivedObj.protectedValue; // invalid, cannot be accessed outside of derived classes
derivedObj.derivedFunction(); // Calls derived class method
}
This example demonstrates the relationship between public, private, and protected members in relation to derived classes.
§2 ‘is-a’ Relationship #
Inheritance represents an “is-a” relationship. For example, if Dog inherits from Animal, then a Dog is an Animal.
class Animal {
public:
void eat() {
std::cout << "Animal eats" << std::endl;
}
};
class Dog : public Animal {
public:
void bark() {
std::cout << "Dog barks" << std::endl;
}
};
Here, Dog inherits the behavior eat() from Animal, but also has its own behavior bark(). Thus, a Dog is an Animal, but it can do more things specific to a dog.
There are different types (or forms) of inheritance that can occur between C++ classes.
- Single Inheritance: A class inherits from one base class.
- Multiple Inheritance: A class inherits from more than one base class.
- Multilevel Inheritance: A class is derived from a class which is itself derived from another class.
- Hierarchical Inheritance: Multiple classes inherit from the same base class.
We will focus on single inheritance in this lab.
§3 Public, Protected, and Private Inheritance #
The type of inheritance (public, protected, or private) controls the visibility of the base class members (i.e., data members and member functions) in the derived class. This directly affects the Access specifiers for individual member variables and methods.
Inheritance is controlled by the keyword after the : symbol:
class Dog : public Animal {}
class Dog : protected Animal {}
class Dog : private Animal {}
- Public Inheritance: Inherited members from the base class retain their access levels (
publicmembers staypublic,protectedmembers stayprotected). - Protected Inheritance:
publicandprotectedmembers of the base class becomeprotectedin the derived class. - Private Inheritance:
publicandprotectedmembers of the base class becomeprivatein the derived class.
Example:
class Base {
public:
int a;
protected:
int b;
private:
int c;
};
class PublicDerived : public Base {
// a is public, b is protected, c is not accessible
};
class ProtectedDerived : protected Base {
// a is protected, b is protected, c is not accessible
};
class PrivateDerived : private Base {
// a is private, b is private, c is not accessible
};
int main() {
Base baseobj; // a is accessible, b & c are not
PublicDerived publicObj; // a is accessible, b & c are not
ProtectedDerived protectedObj; // no base members are accessible here
PrivateDerived privateObj; // no base members are accessible here
}
§4 Slicing #
Inheritance “slicing” occurs when a derived class object is assigned to a base class object, causing the derived class’s additional data members to be “sliced off,” or lost. This can lead to unintended behavior.
Slicing example:
class Base {
public:
int baseValue;
void baseFunction() {
std::cout << "Base class function" << std::endl;
}
};
class Derived : public Base {
public:
void derivedFunction() {
std::cout << "Derived class function" << std::endl;
}
};
int main() {
// Create an object of the Derived class
Derived derivedObj;
// Slicing happens here!
Base baseObj = derivedObj; // The derived members are "sliced" off.
baseObj.baseFunction();
// All derived behavior is now lost, only "Base" behavior and members are accessible
// baseObj.derivedFunction(); // won't work
}
In this example, all member data and functions provided by the Derived class are lost due to slicing. This is a limitation of the C++ type system. We can avoid this problem by utilizing pointers.
§5 Pointers and Inheritance (Function Hiding) #
A key feature of inheritance is that a pointer to a base class can point to an object of a derived class, without suffering from the “slicing” problem, but a naive use of pointers will likely not behave as you would expect.
class Base {
public:
void show() {
std::cout << "Base class function" << std::endl;
}
};
class Derived : public Base {
public:
void show() {
std::cout << "Derived class function" << std::endl;
}
};
int main() {
Base* basePtr; // empty pointer of type Base
Derived derivedObj; // declare object of type Derived
basePtr = &derivedObj; // point to derived object instance
basePtr->show(); // Calls Base class method (function hiding)
}
$ g++ example.cpp -o example ; ./example
Base class function
Here, the basePtr variable points to a Derived object, but the call to show() results in the Base class function being called. This is because C++ uses static binding by default, where the function to be executed is determined at compile-time based on the pointer type.
You need to declare a class function as virtual to allow a derived class function to be called in this way.
§6 Virtual Functions (Dynamic Binding) #
To allow the derived class’s version of a function to be called when using a base class pointer, we can use virtual functions. You declare a function as “virtual” by simply adding the virtual keyword before the function declaration.
class Base {
public:
// method is declared as virtual to allow for overriding
virtual void show() {
std::cout << "Base class show function" << std::endl;
}
};
class Derived : public Base {
public:
void show() {
std::cout << "Derived class show function" << std::endl;
}
};
int main() {
Base* basePtr;
Derived derivedObj;
basePtr = &derivedObj;
basePtr->show(); // Calls Derived class show function (dynamic binding)
}
$ g++ example.cpp -o example ; ./example
Derived class show function
By marking the show function in the Base class as virtual, C++ uses dynamic binding (late binding), and the correct version of show() from the Derived class is called at runtime. In other words, virtual functions are explicitly able to be overridden by derived classes.
§7 Override Keyword #
The override keyword is not strictly required to override a virtual function, as a virtual function will be overridden if redefined in a derived class with the exact same signature. However, override ensures that the function is intended to override a base class virtual function. It confirms that the function has the same name and parameter list as in the base class. If there are mistakes such as typos or mismatched parameters, the override keyword will cause the compiler to generate an error, preventing the accidental creation of a new function rather than overriding the base class function.
Example mistake that override will protect you from:
class Base {
public:
virtual void display() const {
std::cout << "Base class display function" << std::endl;
}
};
class Derived : public Base {
public:
// we intend to override, but if there's an error in the signature, it won't be caught
void display() { // Forgot the 'const'
std::cout << "Derived class display function" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->display(); // Calls Base class display (const version was not overridden properly)
delete basePtr;
}
Here, when the display() method is called, the base class declaration of the function is executed. The programmer meant to call the derived class version of display(), but due to a mistake, the method was not properly overridden. The override keyword would have prevented this issue.
§8 Virtual Destructors #
When using inheritance, it’s important to use a virtual destructor to ensure proper cleanup when deleting a derived class object through a base class pointer.
class Base {
public:
// declare a base class destructor as `virtual` to allow overriding
// this ensures derived destructors are called when using a Base class pointer
virtual ~Base() {
std::cout << "Base destructor called" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor called" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
// Calls Derived's and then Base's destructor, thanks to the `virtual` keyword
delete basePtr;
}
$ g++ example.cpp -o example ; ./example
Derived destructor called
Base destructor called
Without a virtual destructor, only the Base class destructor would be called, potentially causing a memory leak if the derived class allocated dynamic memory.
§9 Inheritance Example #
Here is an example demonstrating inheritance, overriding, and pointers.
// Base class declaration
class Base {
public:
int baseValue; // Public data member of the base class
// Constructor for the base class, initializes baseValue
Base(int value) : baseValue(value) {}
// Virtual function in the base class
// This function can be overridden by derived classes.
virtual void show() const {
std::cout << "Base class show function. baseValue: " << baseValue << std::endl;
}
// Non-virtual function in the base class
// This function cannot be overridden in derived classes.
void baseFunction() const {
std::cout << "Base class function" << std::endl;
}
};
// Derived class declaration, inheriting from Base
class Derived : public Base {
public:
int derivedValue; // Additional data member in the derived class
// Constructor for the derived class
// Calls the base class constructor to initialize baseValue
// and also initializes derivedValue.
Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {}
// Override the base class virtual function
// This replaces the base class implementation when called on a derived object.
void show() const override {
std::cout << "Derived class show function. baseValue: " << baseValue
<< ", derivedValue: " << derivedValue << std::endl;
}
// New Function specific to the derived class
// This function does not exist in the base class.
void derivedFunction() const {
std::cout << "Derived class function" << std::endl;
}
};
int main() {
// Creating an object of the Base class and initializing baseValue to 10
Base baseObj(10);
// Creating an object of the Derived class, initializing baseValue to 20 and derivedValue to 30
Derived derivedObj(20, 30);
// Base class pointer pointing to a Base object
Base* basePtr = &baseObj;
basePtr->show(); // Calls Base class show function (since basePtr points to a Base object)
basePtr->baseFunction(); // Calls Base class specific function (non-virtual, not overridden)
// Base class pointer pointing to a Derived object
basePtr = &derivedObj;
basePtr->show(); // Calls Derived class show function (because it's virtual and overridden)
basePtr->baseFunction(); // Still calls Base class function (non-virtual, not overridden)
// Derived class pointer pointing to a Derived object
Derived* derivedPtr = &derivedObj;
derivedPtr->show(); // Calls Derived class show function (overridden)
derivedPtr->derivedFunction(); // Calls Derived class specific function
}
$ g++ example.cpp -o example ; ./example
Base class show function. baseValue: 10
Base class function
Derived class show function. baseValue: 20, derivedValue: 30
Base class function
Derived class show function. baseValue: 20, derivedValue: 30
Derived class function
This code demonstrates how a base class pointer can point to both base and derived class objects, and how virtual functions allow for calling the derived class version of a function and how non-virtual functions are not overridden and always call the base class version. It also highlights the use of constructors to initialize both base and derived class members.
§10 Further Reading #
- Textbook: Chapter 11.11 Inheritance
- cplusplus.com Inheritance tutorial: https://cplusplus.com/doc/tutorial/inheritance/
§11 Questions #
- What is inheritance in C++ and why is it used?
- How do access specifiers for
public,private, andprotectedspecifically affect class inheritance in C++?
- What will be the output of the following code?
class Base {
public:
void show() {
std::cout << "Base class function" << std::endl;
}
};
class Derived : public Base {
public:
void show() {
std::cout << "Derived class function" << std::endl;
}
};
int main() {
Base* basePtr;
Derived derivedObj;
basePtr = &derivedObj;
basePtr->show();
}
- What is the purpose of the
virtualkeyword in C++?
- What happens if you do not declare a base class destructor as
virtual?
- In the following code, what will the output be?
class Base {
public:
virtual void show() {
std::cout << "Base class show function" << std::endl;
}
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show function" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->show();
delete basePtr;
}
- Are there any errors in the previous question’s code snippet?
- What is the difference between function overriding and function hiding? When does each occur?
- What does the
overridekeyword do in C++?
- is the
overridekeyword mandatory when overriding derived methods?
- What is slicing in C++ inheritance and how can it be avoided?
- What is the difference between function overriding and function overloading?