Classes and Encapsulation
Introduction
In C you grouped data with struct and wrote separate functions that took a pointer: motor_set_rpm(&m, 9000). Nothing stopped another file from changing m.rpm directly — or from forgetting to call init before start.
A class is a struct plus member functions and access control. You decide what outsiders may touch (public) and what stays internal (private). That is encapsulation — hiding implementation so the rest of the firmware depends on behaviour, not on register addresses scattered in globals.
From struct to class
C style:
1 2 3 4 5 6 7 8 9 | |
C++ style:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Callers use motor.setRpm(9000) — they do not reach into rpm_ unless you expose it.
In plain terms
A class is a module with a door. public is the reception desk — safe to visit. private is the machine room — only class members go inside. You fix bugs in the machine room without rewiring every caller.
Access specifiers
| Specifier | Who can access |
|---|---|
public |
Anyone with an object |
private |
Only member functions of this class |
protected |
This class and derived classes (inheritance — advanced topic) |
Default for class: private.
Default for struct: public (C compatibility).
Embedded style guides often say: data members private, methods public — keeps invariants (valid ranges, initialized hardware) inside the class.
Member functions and this
Inside a member function, unqualified names refer to the current object:
1 2 3 4 5 6 7 8 9 10 | |
The compiler passes a hidden pointer this — like C's motor_t *self, but automatic. state_ means this->state_.
Interface vs implementation
Header (led.hpp) — what users need:
1 2 3 4 5 6 7 8 9 10 11 | |
Source (led.cpp) — how it works:
1 2 3 4 5 6 7 8 9 10 11 | |
Splitting .hpp / .cpp keeps compile times manageable and hides HAL details — same idea as C headers, with class syntax.
When a class beats a struct
| Prefer class when | Plain struct / C may suffice when |
|---|---|
| Object has invariants (RPM 0–12000) | POD data passed to HAL |
| Multiple operations on same data | One-off config blob |
| You need private state | Entire team owns all fields |
| Lifetime ties to hardware | C struct in DMA buffer (layout fixed) |
DMA and protocol structs are often plain structs with guaranteed layout — C++ classes with virtual functions change memory layout; avoid virtual in those buffers.
Arduino class — you may already use this
1 2 3 4 5 6 7 | |
Servo myServo; myServo.attach(9); — object with methods. Underneath: timers and GPIO. Encapsulation hides the register math.
Relevant topics
- Introduction to C++
- Constructors, RAII, and destructors
- Structures and user types
- References and const correctness
Starting points
- Wrap one C struct + functions from your project in a minimal class with one
privatefield. - Make
rpmorpinprivate — fix compile errors in callers by adding getters/setters. - Draw a box diagram: public methods outside, private fields inside.
- Compare sizeof plain struct vs class with no virtual functions — should match.
Focus points
- Encapsulation reduces coupling — fewer globals, clearer ownership.
- No virtual functions in ISR-shared or DMA memory without careful design.
- Naming:
setRpm/getRpmorset_rpm— pick one style per project. - Classes are not free — vtables and RTTI cost flash; keep objects simple on MCU.
Key points
- Classes combine data and member functions with
public/privateaccess. - Encapsulation hides implementation; callers use the public interface.
- Headers declare, sources implement — same discipline as C modules.
- Use classes for behaviour + state; use plain structs for raw layout (DMA, packets).