Constructors, RAII, and Destructors
Introduction
In C, initialization is manual: motor_init(&m); before use, and cleanup is easy to forget: file_close never called on error path. C++ constructors run when an object is created; destructors run when it goes out of scope or is deleted. Together they enable RAII (Resource Acquisition Is Initialization) — one of the main reasons to use C++ on embedded projects.
If you take one idea from this section, take RAII: tie a resource (pin, mutex, file, DMA buffer) to an object's lifetime so release is automatic.
Constructors — setup on birth
A constructor has the same name as the class and no return type:
1 2 3 4 5 6 7 8 9 | |
Creating the object runs the constructor:
1 2 3 | |
Member initializer list
The : baud_(baud) part is the initializer list — preferred for members before the body runs:
1 2 3 4 5 | |
Use it for const members, references, and base classes — assignment inside { } is too late for those.
Destructors — cleanup on death
A destructor is ~ClassName():
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Even if you return early or throw (where exceptions are enabled), destructor runs when lock goes out of scope.
In plain terms
RAII = "I bought a ticket when I entered; the ticket is destroyed when I leave, and the door locks itself." You cannot forget to unlock if the language guarantees the destructor runs at scope exit.
RAII on embedded (no exceptions needed)
RAII works without exceptions. Scope exit still calls destructors when:
- Block ends with
} - Function returns
- Object is deleted with
delete
GPIO example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Mutex in RTOS wrapper:
1 2 3 4 5 6 7 8 9 | |
Same pattern as std::lock_guard in desktop C++ — you can implement a thin version without full STL.
Construction order and static objects
Within one object: base classes → members in declaration order → constructor body.
Global / static objects: constructed before main (order between translation units is a classic footgun). On embedded, many teams avoid non-trivial static constructors and use explicit init() for predictability — see Embedded C++ subset.
1 2 | |
Prefer:
1 2 | |
Or a plain C init if your toolchain's static init is unclear.
new and delete — use carefully
Constructors pair with dynamic allocation:
1 2 3 | |
On MCU, prefer static or pool allocation; if you use new, handle out-of-memory (new (std::nothrow) returns nullptr). Destructor runs on delete.
Smart pointers (std::unique_ptr) wrap delete in RAII — useful on embedded Linux; on bare metal often replaced with custom pool handles.
Copy and move (awareness)
C++ can copy or move objects. Embedded firmware often deletes copy on hardware-owning classes:
1 2 3 4 5 6 | |
Prevents two objects from thinking they own the same peripheral. Details in advanced courses; rule of thumb: disable copy for drivers unless you mean to duplicate.
Relevant topics
- Classes and encapsulation
- Dynamic memory and I/O
- Embedded C++ subset
- Operating systems — mutex discipline
Starting points
- Write
ScopedLockaround an existing mutex in your RTOS project. - Trace one constructor in Arduino
Serial.beginpath conceptually — what gets configured? - List three resources in your code that need cleanup — map each to a destructor plan.
- grep
staticglobals in a C++ firmware tree — flag any with non-trivial constructors.
Focus points
- Destructor must not throw (if exceptions exist) and must be fast in ISR context — do not destroy complex objects in ISR.
- RAII replaces
goto cleanupchains in C — still document ownership. - Static initialization order — avoid surprises; prefer explicit init on MCU.
- Delete copy on peripheral-owning classes unless shallow copy is safe.
Key points
- Constructors initialize objects; destructors clean up when lifetime ends.
- RAII binds resources to scope — locks, pins, files release automatically.
- Initializer lists are the idiomatic way to set members.
- On embedded, prefer scope-based cleanup over manual
free/close/unlockscattered in code.