140. Reusing Base Initialization

In embedded firmware, multiple peripheral drivers often require the same mandatory initialization steps, such as:

  • Enabling a shared peripheral clock
  • Resetting common control registers

This logic must be written once and reused by multiple derived drivers to avoid duplication and inconsistency.

Your task is to model this design using inheritance.

Step 1: Base Driver

Create a BaseDriver class that:

  • Provides a function named initBase()
  • When called, prints exactly:

    Base driver init start
    Base driver init complete
    

This function represents shared hardware initialization that all drivers must perform.

Step 2: Derived Drivers

Create two derived driver classes:

  • SpiDriver
  • I2cDriver

Each derived class must:

  • Inherit publicly from BaseDriver
  • Provide its own initialization function:
    • initSpi() for SpiDriver
    • initI2c() for I2cDriver
  • Inside its initialization function:
    • Call initBase()
    • Print its own driver-specific message

Required output messages:

SPI driver initialized
I2C driver initialized

Step 3: main()

In main():

  • Read one integer value mode
  • If mode == 0:
    • Create a SpiDriver
    • Call initSpi()
  • If mode == 1:
    • Create an I2cDriver
    • Call initI2c()

This selection simulates choosing different peripherals at runtime while reusing the same base initialization logic.

Input

One integer mode

  • 0 → SPI driver
  • 1 → I2C driver

Program Flow (Mandatory Order)

  1. Read integer mode
  2. Create selected derived driver object
  3. Call derived driver initialization function
  4. Base driver initialization prints
  5. Derived driver-specific initialization prints

 

Example 1

Input:

0

Output:

Base driver init start
Base driver init complete
SPI driver initialized

 

Example 2

Input:

1

Output:

Base driver init start
Base driver init complete
I2C driver initialized

 

Constraints (Strict)

  • Use inheritance only
  • Both derived drivers must inherit publicly from BaseDriver
  • initBase() must be implemented only once in BaseDriver
  • Each derived driver must explicitly call initBase()
  • Do NOT use:
    • Virtual functions
    • Dynamic memory allocation (new, malloc)
    • Composition (no base object as a member)
  • Output text and order must match exactly
  • Use only standard input and output

 

 

 

Need Help? Refer to the Quick Guide below

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).