An ADCSensor class processes readings from an Analog-to-Digital Converter (ADC). Due to hardware circuit imperfections, the sensor signal has a permanent DC noise offset of 100 units. This means even when the input is zero, the sensor reads 100. Currently, the class exposes this calibration detail poorly. It allows users to access raw values directly or perform the subtraction manually. This often leads to bugs where users forget to handle the "negative undershoot" case (where a raw reading is slightly below the offset due to noise), resulting in invalid negative values for a unipolar sensor. Your task is to encapsulate this logic:
getCalibratedSample(int raw_val).raw < offset), it must be clamped to 0 (saturation).Program Flow:
ADCSensor (Internal offset is fixed at 100).N (number of samples).N times:raw_input (simulating a hardware register read).Input Format:
N (number of samples).N lines: Integer raw_input.Output Format:
Sample: <value>Example:
Example 1
Input:
3
250
100
50Output:
Sample: 150
Sample: 0
Sample: 0
Constraints:
N range: 1 to 20raw_input range: 0 to 4095 (Standard 12-bit ADC range)
Encapsulation is the mechanism of bundling data (variables) and methods (functions) together into a single unit (a Class) and restricting direct access to some of that object's components.
It is not just about "hiding secrets"; it is about System Integrity. By hiding internal data (private), you ensure that the object cannot be put into an invalid or dangerous state by external code. The object manages its own state through a controlled interface (public).
C++ provides three keywords to control who can see and modify class members.
| Specifier | Accessible By | Usage Scenarios |
|---|---|---|
public | Everyone (External code, main()). | The Public API (functions users are meant to call). |
private | Only the Class itself. | Internal state, helper functions, hardware register pointers. |
protected | The Class + Derived Classes (Children). | Internal logic shared with children but hidden from the world. |
The "Safe Hardware" Example
Imagine a Motor Driver where the duty cycle must never exceed 90% (safety limit) and never be negative.
class MotorDriver {
private:
// DATA HIDING:
// If this were public, a user could write 'duty_cycle = 200;' and burn the motor.
float duty_cycle;
uint32_t* pwm_reg;
// INTERNAL HELPER:
// A complex hardware sequence user doesn't need to see.
void trigger_update() {
*pwm_reg = (uint32_t)(duty_cycle * 1000);
}
public:
// CONSTRUCTOR: Initializes state safely.
MotorDriver() : duty_cycle(0.0f) { /* ... */ }
// PUBLIC INTERFACE (Setter):
// Contains Validation Logic (The "Invariant").
void setSpeed(float speed) {
if (speed > 90.0f) speed = 90.0f; // Clamp to safe max
if (speed < 0.0f) speed = 0.0f; // Clamp to safe min
duty_cycle = speed; // Update internal state
trigger_update(); // Update hardware
}
// PUBLIC INTERFACE (Getter):
// Read-only access.
float getSpeed() const {
return duty_cycle;
}
};The friend Keyword (Controlled Breach):
Sometimes, you want one specific external class or function to access your private members without making them public to the whole world.
LinkedList accessing Node internals).class Box {
private:
int secret_data;
friend class TestSuite; // 'TestSuite' can now touch 'secret_data'
};Struct vs. Class:
Technically, they are almost identical. The only difference is the default encapsulation level.
class: Members are private by default. (Prefers Hiding).struct: Members are public by default. (Prefers Exposure).struct for "Plain Old Data" (POD) where there are no invariants to protect (e.g., Coordinate {x, y}). Use class for everything else.Maintaining Invariants:
An invariant is a condition that must always be true.
< BUFFER_SIZE".head is public, user code buf.head++ might overflow. If head is private, the push() function guarantees the wrap-around logic (head = (head + 1) % SIZE).Read-Only Hardware:
Many sensors provide data that should be read but never written to by software.
private and provide only a getVal() function. No setVal() means the data is effectively read-only.Decoupling Implementation:
You can change the internal logic without breaking user code.
timer_counter variable directly, their code breaks.timer.getTime(), you just update the function body. Their code remains untouched.The Fear: "Calling getSpeed() adds function call overhead (stack push/pop). Direct access motor.speed is faster."
The Reality: C++ compilers are incredibly smart. If you define the getter in the header file:
// In header
inline float getSpeed() const { return duty_cycle; }The compiler performs Inlining. It replaces the function call with the direct assembly instruction to read the memory address.
Result: You get the safety of Encapsulation with Zero Performance Penalty.
| Pitfall | Details |
|---|---|
| ❌ Getters/Setters for Everything | Don't automatically create getVariable() and setVariable() for every private member. This breaks encapsulation by exposing internals. Only expose what is necessary for the API. |
| ❌ Returning Non-Const Pointers | If you have private: int* buffer; and a public function int* getBuf() { return buffer; }, you have broken encapsulation. The user can now trash your private memory through that pointer. Fix: Return const int* or a safe copy. |
| ❌ Friend Abuse | Overusing friend turns your code into "Spaghetti Code" where everything touches everything else. Use it only when strictly necessary. |
| ✅ The "Const Correctness" | Always mark getters as const (e.g., int get() const). This assures the compiler and the user that this function will not modify the object's state. |