Understanding Inheritance in C++
Overview
Inheritance is one of the core principles of object-oriented programming, enabling the creation of new classes based on existing ones. This mechanism allows for code reuse at the class level, making it a fundamental concept in C++.
Concept and Definition
Concept
Inheritance enables extending an existing class by inheriting its properties and behaviors. The original class is referred to as the base class or parent class, while the newly created class is known as the derived class or child class. This relationship facilitates code reusability and hierarchical design.
Definition
The syntax for defining inheritance in C++ is:
DerivedClass : AccessSpecifier BaseClass
Access specifiers define how members of the base class are accessible in the derived class:
public: Members remain public in the derived class.protected: Members become protected in the derived class.private: Members are inaccessible in the derived class.
It's recommended to explicitly specify the access specifier rather than relying on default behavior.
Assignment Between Base and Derived Objects
A derived class object can be assigned too a base class object, pointer, or reference. This operation is often described as "slicing" because only the base part of the derived object is copied.
However, assigning a base class object to a derived class object is not allowed.
Pointer and reference conversions from base to derived types are possible using casting, but they are unsafe unless the base pointer actually points to a derived object.
Example:
class Person {
protected:
std::string name;
std::string sex;
int age;
};
class Student : public Person {
public:
int studentId;
};
void Test() {
Student student;
// Assigning derived to base
Person person = student;
Person* ptr = &student;
Person& ref = student;
// This will cause a compilation error
// student = person;
// Safe cast if base pointer points to derived
Student* derivedPtr = static_cast<Student*>(ptr);
}
Scope in Inheritance
Each class in an inheritance hierarchy maintains its own scope. If both the base and derived class have members with the same name, the derived class member hides the base class member. This applies even to functions, where matching function signatures trigger hiding.
Example:
class Base {
public:
void show() { std::cout << "Base\n"; }
int value = 1;
};
class Derived : public Base {
public:
void show() { std::cout << "Derived\n"; }
int value = 2;
};
int main() {
Derived d;
d.show(); // Calls Derived::show()
return 0;
}
Default Member Functions in Derived Classes
When a class inherits from another, the default constructors, destructors, copy constructors, and assignment operators follow speicfic calling sequences:
- Constructors: Derived constructor calls base constructor first.
- Copy constructors and assignment operators: Base operations occur before derived.
- Destructors: Derived destructor runs before base destructor.
Inheritance and Static Members
Static members of a base class are shared among all derived classes. They exist once per class hierarchy regardless of how many derived classes there are.
Friendship does not propagate through inheritance. A friend of the base class cannot access protected or private members of the derived class.
Diamond Inheritance and Virtual Inheritance
Diamond inheritance occurs when a class inherits from two or more classes that share a common base class. This leads to ambiguity and data duplication.
To resolve this, C++ uses virtual inheritance:
class A {
public:
int data;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
Virtual inheritance ensures that only one instance of the base class exists in the derived class, eliminating redundancy and resolving ambiguity.
Composition vs. Inheritance
Composition is generally preferred over inheritance. It represents a "has-a" relationship, where one class contains an instance of another. In contrast, inheritance represents an "is-a" relationship.
Inheritance should be used when there is a clear is-a relationship and when polymorphism is required. Otherwise, composition provides better flexibility and encapsulation.
In summary, while inheritance supports code reuse, composition offers greater design flexibility and lower coupling.