Inheritance (Code Reusability)

cardimg

Inheritance is a mechanism where a new class (Derived Class) acquires the properties and behaviors (variables and functions) of an existing class (Base Class).

It enables the "Is-a" Relationship (e.g., a Button is a GPIO_Device).

This allows you to write generic code in a Base class (like Packet) and extend or specialize it in Derived classes (like WiFiPacket, BluetoothPacket) without rewriting the common logic.

Syntax & Usage

1. Basic Declaration

Use the : symbol followed by the access mode (usually public).

// Base Class (Parent)
class SerialPort {
public:
    void open(int baud) { /* Generic open logic */ }
    void close() { /* Generic close logic */ }
};

// Derived Class (Child)
// Syntax: class Child : access_specifier Parent
class UART : public SerialPort {
public:
    // UART inherits open() and close() automatically.
    
    // Adds new specific functionality
    void set_parity(int p) { /* ... */ }
};

UART u;
u.open(9600);    // Calls Base function
u.set_parity(1); // Calls Derived function

2. Access Specifiers (protected)

Inheritance introduces a new access level: protected.

  • private: Visible only to the Base class. (Derived classes cannot see it).
  • protected: Visible to the Base class and Derived classes. (Outsiders cannot see it).
  • public: Visible to everyone.
class Sensor {
protected:
    int raw_adc_value; // Children can access this directly
private:
    int secret_key;    // Children CANNOT access this
};

class TempSensor : public Sensor {
public:
    void read() {
        raw_adc_value = HW_Read(); // ✅ Allowed (protected)
        // secret_key = 0;         // ❌ Error (private)
    }
};

3. Constructor Execution Order

When you create a Derived object, the Base constructor runs first, then the Derived constructor.

When destroyed, the order is reversed (Derived destructor first, then Base).

class Base {
public:
    Base(int x) { /* Init Base */ }
};

class Derived : public Base {
public:
    // Must explicit call Base constructor in initializer list
    Derived(int x, int y) : Base(x) { 
        /* Init Derived */ 
    }
};

Memory Layout

A Derived class object is essentially the Base class object with the new fields "glued" to the end of it. It forms a single contiguous block of memory.

Address OffsetContentBelongs To
0x00Base::var1Base Class
0x04Base::var2Base Class
0x08Derived::new_varDerived Class

Relevance in Embedded/Firmware

1. Hardware Abstraction (HAL)

This is the standard architecture for portable drivers.

  • Base Class: Display (Defines generic drawPixel, drawRect, clear).
  • Derived Class: ILI9341_Display (Implements drawPixel for specific hardware).
  • Application: Writes to Display*. It doesn't care which screen is connected.

2. Generic Protocol Handling

If you have multiple communication packets (Command Packet, Data Packet, Ack Packet) that all share a Header (ID, Length) and CRC, you create a BasePacket class.

  • BasePacket handles CRC calculation and Header parsing.
  • DataPacket adds the payload buffer. This saves Flash memory by not duplicating the CRC logic 3 times.

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Object SlicingIf you assign a Derived object to a Base variable (Base b = derived;), the derived parts are sliced off. Always use Pointers (Base*) or References (Base&) when dealing with hierarchy.
❌ Missing Virtual DestructorIf you delete a Derived object via a Base pointer (Base* b = new Derived(); delete b;), the Derived destructor will NOT run unless the Base destructor is marked virtual. This causes memory leaks.
❌ Multiple InheritanceInheriting from two classes (class C : public A, public B) is possible but dangerous (ambiguity, diamond problem). Avoid it in firmware; use Composition instead.
✅ Composition over InheritanceIf a class "Has a" dependency (e.g., A Car has an Engine), use a member variable, not inheritance. Only use Inheritance for "Is a" relationships (e.g., A Car is a Vehicle).

 

 

 

Concept understood? Let's apply and learn for real

Practice now