93. SensorConfig Initialization

Create a class SensorConfig that represents configuration data for a digital sensor in an embedded system.

Each sensor must be initialized with valid configuration data at startup, specifically a sensor ID and a calibration offset. The system must not allow uninitialized sensor objects.

The class must store the latest calibrated sensor value and update it whenever new raw data is received.

Class Requirements

The class SensorConfig must contain the following private data members:

  • int id — unique sensor identifier
  • int offset — calibration offset applied to raw readings
  • int lastValue — last calibrated value (must be initialized to 0)

Constructor Requirements

The class must define only one constructor:

SensorConfig(int sensorId, int calibrationOffset)

The constructor must:

  • Assign sensorId to id
  • Assign calibrationOffset to offset
  • Initialize lastValue to 0

❗ A default constructor must not be used.

Public Member Functions

  • void update(int raw)
    • Applies calibration using the formula:
      • lastValue = raw + offset
        
  • int read()
    • Returns the current value of lastValue

Main Function Behavior

In main():

  1. Read two integers: id and offset
  2. Create a SensorConfig object using the parameterized constructor
  3. Read two raw sensor values: r1 and r2
  4. Call update(r1) followed by update(r2)
  5. Print the final calibrated value returned by read()

Input Format

id offset
r1 r2
  • All values are integers
  • Offsets may be negative

Output Format

<final_calibrated_value>
  • Output only the final calibrated value
  • No extra spaces or newlines

 

Example Input

10 3
20 25

Example Output

28

Explanation

  • Offset = 3
  • update(20)lastValue = 23
  • update(25)lastValue = 28
  • Final output is 28

 

Constraints

  • Only the parameterized constructor must be used
  • Calibration offset must be applied on every update
  • Output formatting must match exactly
  • Use standard integer arithmetic

 

 

 

 

Need Help? Refer to the Quick Guide below

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.