Dependency Injection and References: A Complete Guide¶
Table of Contents¶
- Understanding Your Questions
- References Must Be Initialized
- Simple Examples with
int - Why Initializer Lists Are Required
- Method Parameters vs Member Variables
- Complete Examples
Understanding Your Questions¶
You asked some great questions! Let's address them one by one:
-
✅ Yes! When you call
TestingSequence testSeq(dmm, asa, perif, hmi), the initializer list sets your private referencesdmm_,asa_,perif_,hmi_equal to the constructor arguments. This allows all class methods to access these references. -
⚠️ Almost! Your method example needs a small syntax fix - let's clarify this below.
-
✅ 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:
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:¶
- References MUST be initialized when declared - you can't assign to them later
- Initializer lists are REQUIRED for reference member variables - they run before the constructor body
- Method parameters (
func(Perif& perif)) are temporary - they only exist during the function call - Member variables (
Perif& perif_) persist for the object's lifetime - must be initialized in constructor - 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!