Constructors (Automatic Initialization)

cardimg

A Constructor is a special member function that runs automatically when an object is created. Its purpose is to guarantee that the object starts in a valid state (e.g., pointers assigned, hardware initialized, invariants checked) before any other code uses it.

It has no return type and shares the same name as the class.

Syntax & Usage

1. Basic & Parameterized Constructors

The most common forms used to initialize variables.

class GPIO {
    int pin;
public:
    // 1. Default Constructor (No args)
    GPIO() { pin = 0; }

    // 2. Parameterized Constructor (With args)
    GPIO(int p) { pin = p; }
};

GPIO led;        // Calls GPIO()
GPIO motor(9);   // Calls GPIO(int)

2. Member Initializer Lists (Crucial for Efficiency)

Initializes variables before the constructor body runs. This is mandatory for const members and References, and avoids "double-writing" variables.

class Sensor {
    const int id;
    int& bus_ref;
public:
    // Syntax: : member(value), member(value)
    Sensor(int i, int& bus) : id(i), bus_ref(bus) { 
        // Body is empty; work is already done.
    }
};

3. Advanced Variants (explicit, Copy, delete)

These control how objects are created or copied, preventing dangerous bugs.

  • explicit: Prevents accidental implicit conversions (e.g., passing int where a Driver object is expected).
  • Copy Constructor: Defines how to clone an object (crucial for Deep Copies of pointers).
  • = delete: Disables a constructor (used for Singletons or unique hardware drivers).
class Driver {
    int* buffer;
public:
    // PREVENT implicit conversion from int to Driver
    explicit Driver(int size) { buffer = new int[size]; }

    // CUSTOM Copy Constructor (Deep Copy)
    Driver(const Driver& other) {
        buffer = new int[10]; 
        memcpy(buffer, other.buffer, 10 * sizeof(int));
    }

    // DISABLE Copying entirely (Common for hardware drivers)
    // Driver(const Driver&) = delete; 
};

Initialization vs Assignment Flow

FeatureAssignment (Inside Body)Initializer List (: x(10))
MechanismVariable created (default), then overwritten.Variable created with value.
PerformanceSlower (Two steps).Faster (One step).
ConstraintsCannot init const or Reference.Required for const/Reference.

Relevance in Embedded/Firmware

1. Impossible-to-Forget Initialization

In C, forgetting UART_Init() crashes the system. 

In C++, constructors force you to provide configuration (e.g., Baud Rate) at creation time. The object never exists in an uninitialized state.

2. RAII (Resource Acquisition Is Initialization)

The "Scope Lock" pattern: A constructor acquires a resource (disables interrupts, takes a mutex), and the Destructor releases it.

{
    InterruptLock lock; // Constructor: __disable_irq();
    // Critical Section
} // Destructor: __enable_irq(); automatic at end of scope.

3. Unique Hardware Access

Using = delete on copy constructors ensures you don't have two software objects trying to control the same physical UART peripheral, preventing race conditions.

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Heavy WorkAvoid complex hardware delays or checks in constructors for Global/Static objects. They run before main(), which can make the system appear to hang at boot.
❌ Initializer Order

Members are initialized in the order they are declared in the class, NOT the order in the list.

class A { int y; int x; A(): x(1), y(x) {} } reads uninitialized memory because y is declared first.

❌ Implicit ConversionWithout explicit, void func(Driver d) can be called as func(100), silently creating a 100-byte driver. This is confusing and dangerous.
✅ Default ConstructorIf you define any constructor, the compiler removes the default (empty) one. You must explicitly add GPIO() = default; if you still need it.

 

 

 

 

Concept understood? Let's apply and learn for real

Practice now