Skip to content
BoKSA

Classes and Encapsulation

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
typedef struct {
    int port;
    double rpm;
} motor_t;

void motor_set_rpm(motor_t *m, double rpm) {
    m->rpm = rpm;
    /* hardware writes hidden in this function */
}

C++ style:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Motor {
public:
    void setRpm(double rpm) {
        rpm_ = rpm;
        applyToHardware();
    }

    double getRpm() const { return rpm_; }

private:
    int port_;
    double rpm_;

    void applyToHardware();  /* only class can call */
};

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
class Led {
public:
    void on()  { state_ = true;  writePin(); }
    void off() { state_ = false; writePin(); }

private:
    bool state_;
    int pin_;
    void writePin();
};

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
class Led {
public:
    explicit Led(int pin);
    void on();
    void off();
    bool isOn() const;

private:
    int pin_;
    bool state_;
};

Source (led.cpp) — how it works:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include "led.hpp"
#include "hal_gpio.h"

Led::Led(int pin) : pin_(pin), state_(false) {
    gpio_init(pin_);
}

void Led::on() {
    state_ = true;
    gpio_write(pin_, 1);
}

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
class Servo {
public:
    Servo();
    int attach(int pin);
    void write(int angle);
    // ...
};

Servo myServo; myServo.attach(9); — object with methods. Underneath: timers and GPIO. Encapsulation hides the register math.


Relevant topics


Starting points

  1. Wrap one C struct + functions from your project in a minimal class with one private field.
  2. Make rpm or pin private — fix compile errors in callers by adding getters/setters.
  3. Draw a box diagram: public methods outside, private fields inside.
  4. 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 / getRpm or set_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 / private access.
  • 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).