96. ADCChannel Constructor Validation

Create a class ADCChannel that represents an analog-to-digital converter (ADC) configuration on a microcontroller. The object must validate inputs inside the constructor, ensuring the channel and resolution are always safe and legal.

Details you must implement:

  • Private members:
    • int channel
    • int resolution
  • Constructor Requirements:
    • Create a constructor:
      • ADCChannel(int ch, int res)
    • It must enforce the following rules:
      • Valid channels are 0 to 15
        • If ch < 0, set channel = 0
        • If ch > 15, set channel = 15
      • Valid resolutions are 8, 10, or 12 bits
        • If any other value is provided, set resolution = 12 (safe default)
  • Public methods:
    • void print()
      • Should display exactly:
        • CH=<channel> RES=<resolution>

Program behavior (main()):

  • Read ch and res
  • Construct an ADCChannel object
  • Print its internal values

 

Example 1

Input:

3 10

Output:

CH=3 RES=10 

 

Example 2

Input (invalid channel and invalid resolution):

20 9

Example Output:

CH=15 RES=12 

Explanation:

  • 20 → clamped to 15
  • 9 → not in {8,10,12} → default = 12

 

Constraints:

  • You must validate channel and resolution inside the constructor.
  • Resolution must fall back to 12 when invalid.
  • Output must match exactly.

 

 

 

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.