Core Concepts of Object-Oriented C++: Classes, Inheritance, and Composition Explained!

In C++ Object-Oriented Programming (OOP), classes, inheritance, and composition are the three pillars for building complex programs. They not only help us achieve code reuse but also make the program structure clear and easy to maintain. Today, we will thoroughly understand these three core concepts from theory to practice.

1. Classes: The “Basic Unit” of Object-Oriented Programming

A class is the foundation of C++ object-oriented programming; it acts as a “template” that encapsulatesdata (attributes) and methods for manipulating data (behaviors), used to describe the common characteristics of a category of things.

For example: Defining a “Person” class

class Person {private:    // Private members: data hidden, accessible only within the class    string name;    int age;public:    // Public members: provide interface to the outside    Person(string n, int a) : name(n), age(a) {}  // Constructor    void sayHello() {  // Behavior method        cout << "Hello everyone, I am " << name << ", I am " << age << " years old" << endl;    }    void setAge(int a) {  // Encapsulation: control data modification through methods        if (a > 0) age = a;    }};

Core Features of Classes:

  • Encapsulation: Control access permissions through private and public, hide internal implementation, and expose only necessary interfaces.
  • Instantiation: Create concrete objects using classes (e.g., Person p(“Zhang San”, 20);), where the object is a concrete embodiment of the class.

In simple terms, a class is like a “blueprint,” and an object is the “physical” item created according to the blueprint.

2. Inheritance: Standing on the Shoulders of the “Parent Class”

When multiple classes share common attributes and behaviors, we can abstract these commonalities into abase class (parent class), and other classes can reuse the base class’s code throughinheritance while adding their own features.

The Core of Inheritance: The “is-a” Relationship

For example, both “students” and “teachers” are “people,” so we can let<span>Student</span> and <span>Teacher</span> classes inherit from the <span>Person</span> class:

// Base class (parent class)class Person {protected:  // Protected members: accessible to subclasses, not to outsiders    string name;    int age;public:    Person(string n, int a) : name(n), age(a) {}    void sayHello() {        cout << "Hello everyone, I am " << name << endl;    }};// Derived class (subclass): inherits from Personclass Student : public Person {private:    int studentId;  // Unique attribute of the subclasspublic:    // Subclass constructor: must initialize the parent class first    Student(string n, int a, int id) : Person(n, a), studentId(id) {}    // Unique method of the subclass    void study() {        cout << name << " (Student ID: " << studentId << ") is studying" << endl;    }};

Key Points of Inheritance:

  • Access Control: In public inheritance, the public members of the parent class remain public in the subclass, and protected members remain protected (private members cannot be directly accessed by subclasses).
  • Code Reuse: Subclasses do not need to rewrite the code already present in the parent class (e.g., <span>name</span>, <span>sayHello()</span>).
  • Foundation of Polymorphism: Through virtual functions (<span>virtual</span>), subclasses can override parent class methods, achieving “the same interface, different implementations.”

Note: Inheritance increases the coupling between classes (modifications in the parent class may affect subclasses), so it should not be overused.

3. Composition: Code Reuse Like “Building Blocks”

Composition is another way to reuse code: a class contains objects of another class as members, reflecting a “has-a” relationship.

For example: The relationship between a car and its parts

A car consists of parts like an engine and tires, so the <span>Car</span> class can compose <span>Engine</span> and <span>Tire</span> classes:

// Part class: Engineclass Engine {public:    void start() {        cout << "Engine started" << endl;    }};// Part class: Tireclass Tire {public:    void roll() {        cout << "Tire rolling" << endl;    }};// Car class: Composes Engine and Tireclass Car {private:    Engine engine;  // Contains Engine object    Tire tire[4];   // Contains 4 Tire objectspublic:    void run() {        engine.start();  // Call method of composed object        for (int i = 0; i < 4; i++) {            tire[i].roll();        }        cout << "Car is running..." << endl;    }};

Advantages of Composition:

  • Low Coupling: Classes in composition are relatively independent, and modifications in one class have little impact on another.
  • High Flexibility: Composed objects can be dynamically replaced (e.g., changing to a different model of engine).
  • Realistic Logic: In many scenarios, a “contains” relationship is closer to reality than “inheritance” (e.g., a car “has” an engine, rather than “is” an engine).

4. Inheritance vs Composition: How to Choose?

Often, both inheritance and composition can meet the requirements, but the choice of which method to use directly affects code quality. Remember these two principles:

  • Use inheritance for “is-a”: If A is a type of B (e.g., a student is a type of person), use inheritance.
  • Use composition for “has-a”: If A contains B (e.g., a car contains an engine), use composition.

Counterexample: If you inherit from the “Bird” class to reuse the “fly” method for the “Airplane” class, it violates the “is-a” principle (an airplane is not a bird); in this case, composition should be used (an airplane “has” a flight system).

Design Advice: Prefer composition, as it is more flexible and has lower coupling. Inheritance is suitable for scenarios requiring polymorphism.

Conclusion

  • Classes are the basic units that encapsulate data and behaviors, forming the foundation of OOP.
  • Inheritance achieves code reuse through “is-a” relationships, suitable for building class hierarchies, but be cautious of coupling issues.
  • Composition reuses code through “has-a” relationships, offering greater flexibility and lower coupling, making it the preferred choice in most scenarios.

What pitfalls have you encountered when using inheritance and composition? Feel free to share in the comments!

Leave a Comment