Skip to content

Dependency Injection and References: A Complete Guide

Table of Contents

  1. Understanding Your Questions
  2. References Must Be Initialized
  3. Simple Examples with int
  4. Why Initializer Lists Are Required
  5. Method Parameters vs Member Variables
  6. Complete Examples

Understanding Your Questions

You asked some great questions! Let's address them one by one:

  1. Yes! When you call TestingSequence testSeq(dmm, asa, perif, hmi), the initializer list sets your private references dmm_, asa_, perif_, hmi_ equal to the constructor arguments. This allows all class methods to access these references.

  2. ⚠️ Almost! Your method example needs a small syntax fix - let's clarify this below.

  3. Exactly right! You cannot assign to a reference after declaration - it MUST be initialized. That's why we use initializer lists.


References Must Be Initialized

The Golden Rule: References MUST be initialized when they are declared. You cannot assign to them later.

// ❌ WRONG - This won't compile!
int x = 5;
int& ref;        // Error: reference must be initialized
ref = x;         // Too late! Can't assign after declaration

// ✅ CORRECT - Initialize immediately
int x = 5;
int& ref = x;    // Must initialize when declaring

Why? A reference is an alias - it's not a separate variable. Once created, it always refers to the same object. C++ doesn't allow "re-seating" (changing what a reference points to).


Simple Examples with int

Let's use int to make this crystal clear:

Example 1: Basic Reference

int main() {
    int number = 42;        // Create an int

    int& ref = number;      // ref is an alias for number
    // ↑ Must initialize immediately!

    ref = 100;              // Changes number to 100
    std::cout << number;   // Prints: 100

    return 0;
}

Example 2: Reference as Function Parameter

void doubleValue(int& num) {  // num is a reference parameter
    num = num * 2;             // Changes the original value
}

int main() {
    int value = 5;
    doubleValue(value);        // Pass by reference
    std::cout << value;        // Prints: 10 (changed!)

    return 0;
}

Example 3: Reference as Member Variable (The Key Example!)

class NumberHolder {
public:
    // Constructor with initializer list
    NumberHolder(int& num) : num_(num) {}
    //                        ↑
    //                    This is the ONLY way to initialize
    //                    a reference member variable!

    void print() {
        std::cout << num_;     // Use the reference
    }

    void doubleIt() {
        num_ = num_ * 2;        // Changes the original number!
    }

private:
    int& num_;  // Reference member - MUST be initialized in constructor
};

int main() {
    int myNumber = 42;

    NumberHolder holder(myNumber);  // Pass reference to constructor
    holder.print();                  // Prints: 42
    holder.doubleIt();               // Changes myNumber!
    std::cout << myNumber;           // Prints: 84 (changed!)

    return 0;
}

Example 4: Why You Can't Assign Later

class NumberHolder {
public:
    NumberHolder(int& num) {
        // ❌ WRONG - Can't do this!
        // num_ = num;  // Error: num_ hasn't been initialized yet!

        // ✅ CORRECT - Must use initializer list
        // (See constructor above)
    }

private:
    int& num_;  // This MUST be initialized before constructor body runs!
};

Why? Member variables are initialized BEFORE the constructor body runs. For references, initialization is mandatory, so you MUST use the initializer list.


Why Initializer Lists Are Required

The Problem Without Initializer Lists

class BadExample {
public:
    BadExample(int& num) {
        // ❌ This won't work!
        num_ = num;  // Error: num_ is not initialized!
        // You can't assign to an uninitialized reference
    }

private:
    int& num_;  // Reference MUST be initialized
};

The Solution: Initializer Lists

class GoodExample {
public:
    GoodExample(int& num) : num_(num) {}
    //                      ↑
    //                  Initializer list - runs BEFORE constructor body
    //                  This is where num_ gets initialized

private:
    int& num_;  // Now properly initialized!
};

Timeline: 1. Object is created 2. Initializer list runs ← References MUST be initialized here 3. Constructor body runs ← Too late to initialize references!


Method Parameters vs Member Variables

You asked about this:

func(Perif &perif_)

Let's clarify the difference between: - Method parameters (arguments passed to functions) - Member variables (data stored in the class)

Method Parameters (Temporary References)

class TestingSequence {
public:
    // Method that takes a reference as a PARAMETER
    void func(Perif& perif) {  // ← perif is a parameter name
        // perif is a temporary reference that exists only during this function call
        perif.doSomething();
    }

private:
    Perif& perif_;  // ← perif_ is a member variable (stored in the class)
};

int main() {
    Perif perif;                    // Create object
    TestingSequence tsq(...);       // Create TestingSequence

    tsq.func(perif);                // Pass perif as argument
    //     ↑
    //  This creates a temporary reference parameter
    //  that refers to perif, but only during the function call

    return 0;
}

Key Points: - func(Perif& perif) - perif is a parameter name (can be anything) - Perif& perif_ - perif_ is a member variable name (stored in class) - Method parameters are temporary - they only exist during the function call - Member variables persist for the lifetime of the object

Your Example - Corrected

class TestingSequence {
public:
    TestingSequence(Perif& perif) : perif_(perif) {}
    //                              ↑
    //                          Initialize member variable

    void func(Perif& perifParam) {  
        // ↑ perifParam is a parameter - different from perif_
        // You could also just use the member variable:
        perif_.doSomething();  // Use the member variable

        // Or use the parameter:
        perifParam.doSomething();  // Use the parameter
    }

private:
    Perif& perif_;  // Member variable - initialized in constructor
};

int main() {
    Perif perif;
    TestingSequence tsq(perif);  // Initialize perif_ member variable

    tsq.func(perif);              // Pass perif as parameter
    //                            // (Could use tsq.perif_ directly instead)

    return 0;
}

Complete Examples

Example 1: Simple Class with Reference Member

#include <iostream>

class Counter {
public:
    Counter(int& value) : value_(value) {}
    //                    ↑ Initializer list - REQUIRED for references

    void increment() {
        value_++;  // Changes the original value
    }

    void print() {
        std::cout << "Value: " << value_ << std::endl;
    }

private:
    int& value_;  // Reference member - must be initialized
};

int main() {
    int count = 0;

    Counter counter(count);  // Pass reference to constructor
    counter.print();          // Prints: Value: 0

    counter.increment();      // Changes count!
    counter.print();          // Prints: Value: 1

    std::cout << "Original: " << count << std::endl;  // Prints: Original: 1

    return 0;
}

Example 2: Multiple References

class Calculator {
public:
    Calculator(int& a, int& b) : a_(a), b_(b) {}
    //                           ↑    ↑
    //                    Both must be initialized

    int add() {
        return a_ + b_;  // Uses both references
    }

    void swap() {
        int temp = a_;
        a_ = b_;         // Changes original values!
        b_ = temp;
    }

private:
    int& a_;  // Reference to first number
    int& b_;  // Reference to second number
};

int main() {
    int x = 10;
    int y = 20;

    Calculator calc(x, y);
    std::cout << calc.add() << std::endl;  // Prints: 30

    calc.swap();
    std::cout << "x: " << x << ", y: " << y << std::endl;
    // Prints: x: 20, y: 10 (swapped!)

    return 0;
}

Example 3: Why You Can't Assign Later

class BadExample {
public:
    BadExample(int& num) {
        // ❌ This won't compile!
        // num_ = num;  // Error: reference member 'num_' is not initialized

        // The problem: num_ doesn't exist yet!
        // References MUST be initialized when the object is created
        // That happens in the initializer list, not the constructor body
    }

private:
    int& num_;
};

class GoodExample {
public:
    GoodExample(int& num) : num_(num) {}
    //                      ↑
    //                  This works! Initializer list runs first

private:
    int& num_;
};

Example 4: Real-World Example (Like Your Code)

class TestingSequence {
public:
    // Constructor with dependency injection
    TestingSequence(DMM& dmm, Perif& perif) 
        : dmm_(dmm), perif_(perif) {}
    //   ↑           ↑
    // Initialize both reference members

    void runTest() {
        // Use member variables directly
        float resistance = dmm_.getResistance();
        perif_.activateContact(0);
    }

    // Method that takes a parameter (different from member variable)
    void doSomethingWithPerif(Perif& tempPerif) {
        // tempPerif is a parameter - temporary, only exists during this call
        tempPerif.doSomething();

        // Or use the member variable instead:
        perif_.doSomething();  // Uses the stored reference
    }

private:
    DMM& dmm_;      // Member variable - initialized in constructor
    Perif& perif_;  // Member variable - initialized in constructor
};

int main() {
    // Create the actual objects
    DMM dmm;
    Perif perif;

    // Pass references to constructor (dependency injection)
    TestingSequence testSeq(dmm, perif);
    //                       ↑    ↑
    //                References to dmm and perif

    // Now testSeq has references to dmm and perif
    testSeq.runTest();

    // You could also pass perif as a parameter (but usually use member variable)
    testSeq.doSomethingWithPerif(perif);

    return 0;
}

Summary

Key Takeaways:

  1. References MUST be initialized when declared - you can't assign to them later
  2. Initializer lists are REQUIRED for reference member variables - they run before the constructor body
  3. Method parameters (func(Perif& perif)) are temporary - they only exist during the function call
  4. Member variables (Perif& perif_) persist for the object's lifetime - must be initialized in constructor
  5. Dependency injection means passing dependencies (references) into the constructor rather than creating them inside the class

The Syntax:

class MyClass {
public:
    MyClass(int& num) : num_(num) {}
    //                  ↑
    //              Initializer list syntax:
    //              memberVariable_(parameter)

private:
    int& num_;  // Reference member - must be initialized
};

Why This Matters:

  • Sharing objects - Multiple classes can reference the same object
  • Testing - Easy to swap in mock objects
  • Flexibility - Class doesn't create its dependencies
  • Safety - References can't be null (unlike pointers)

Quick Reference

Concept Syntax When to Use
Reference variable int& ref = value; Must initialize immediately
Reference parameter void func(int& param) Function parameter
Reference member int& member_; Class member variable
Initializer list : member_(param) Initialize reference members
Assignment ref = value; ❌ Can't assign to uninitialized reference

Remember: References are aliases, not separate objects!