Skip to content
BoKSA

Embedded C++ Subset

Embedded C++ Subset

Introduction

C++ on a laptop can use exceptions, gigabytes of heap, and the full standard library. Your MCU has tens to hundreds of kilobytes of flash, limited RAM, no MMU, and hard real-time deadlines. Professional embedded C++ is not "all of C++" — it is C++ with features switched off or restricted on purpose.

This article gives practical rules: what to use freely, what to gate carefully, and what many teams ban in safety-related firmware.


The embedded C++ mindset

Same silicon as C firmware. Extra C++ features must earn their flash, RAM, and determinism cost.

1
2
Desktop C++     →  exceptions, iostream, huge STL, dynamic everything
Embedded C++    →  classes, RAII, namespaces, selected templates, mostly static allocation

In plain terms

C++ on MCU is a toolbox, not the whole hardware store. You bring a small bag: hammer (classes), tape (RAII), labels (namespaces). You leave the crane (exceptions, heavy STL) at home unless the project truly needs it.


Feature guide

Feature Typical on MCU Notes
Classes / RAII Yes Core win
References / const Yes Low cost
Namespaces Yes Organization
enum class Yes Safer than C enum
Templates (limited) Careful Watch flash
new / delete Rare / pool Fragmentation risk
Exceptions Usually off -fno-exceptions; table cost
RTTI Usually off -fno-rtti; dynamic_cast gone
std::iostream Usually no Large; use UART printf
std::vector / strings Linux class MCUs only Heap; use fixed buffers
Threads (std::thread) Prefer RTOS API FreeRTOS, Zephyr
Virtual functions Sparingly vtable per class

Compiler flags you will see

1
arm-none-eabi-g++ -std=c++17 -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections ...
Flag Effect
-fno-exceptions No try/catch; smaller binary
-fno-rtti No runtime type info
-Os Optimize size
-ffunction-sections Linker garbage-collect unused

Arduino and PlatformIO often set these implicitly on AVR/ARM; verify in verbose build log.


Exceptions — usually disabled

1
2
3
4
5
try {
    sensor.read();
} catch (const SensorError &e) {
    /* handler */
}

With -fno-exceptions, this does not compile or is transformed away. Error handling on MCU:

  • Return error codes (enum class Error)
  • Optional std::optional (C++17) on larger targets
  • Assertions in debug builds
  • Safe state + watchdog on fault

Same discipline as good C firmware — C++ does not require exceptions.


Dynamic memory

Dynamic memory and I/O applies: heap may be small. C++ adds:

  • new / delete — avoid in ISRs and tight loops
  • std::string, std::vector — allocate under the hood
  • Smart pointers — still use heap unless custom deleter + pool

Preferred:

  • Static buffers (std::array, C arrays)
  • Memory pools for fixed-size objects
  • Placement new on pre-allocated arena (advanced)

Static initialization

Global C++ objects with constructors run before main:

1
static Motor left(1);  /* constructor runs early */

Order across files is undefined. Many teams:

  • Use init() called from startup code after clocks and HAL
  • Keep globals POD or trivial
  • Document any allowed static constructors

Virtual functions — cost of indirection

1
2
3
4
5
6
7
8
9
class Sensor {
public:
    virtual int read() = 0;  /* pure virtual — abstract */
};

class Bme280 : public Sensor {
public:
    int read() override { /* ... */ }
};

Each object with virtual methods has a vtable pointer — extra RAM per object, indirect call cost. Fine for polymorphic drivers on Linux; on 8 KB RAM MCU, prefer compile-time polymorphism (templates) or plain C function pointers you already know from Pointers in depth.


enum class — use this

1
2
3
4
enum class MotorState { Idle, Running, Fault };

MotorState s = MotorState::Idle;
// if (s == 0)  /* error — no implicit int */

Scoped enumerations prevent the classic C bug if (state = Running).


Mixing with C HAL — recap

  • HAL in C (.c / .h)
  • Application in C++ (.cpp)
  • extern "C" includes
  • No exceptions across C boundaries
  • Callbacks: function pointers still valid

Style guides and MISRA

Automotive and medical projects use MISRA C++ or house rules: subset of language, required patterns, banned constructs. Even student projects benefit from a short team coding standard (naming, no naked new, const discipline).


Arduino / PlatformIO note

Sketches are C++ (setup/loop are functions, Serial is an object). Hiding under the hood: classes, inheritance, some STL. Learning "real" embedded C++ means understanding what the core does without copying every convenience feature to a bare STM32 project.


Relevant topics


Starting points

  1. Read build flags for your board — note -fno-exceptions, -std=.
  2. Audit one project for new, std::string, virtual — list flash/RAM risks.
  3. Rewrite one error path from hypothetical throw to enum class return.
  4. Compare binary size: same logic in C vs minimal C++ class with RAII.

Focus points

  • Subset by policy — document what your team allows.
  • Measure after enabling templates or virtual hierarchies.
  • RTOS APIs over std::thread on typical MCU.
  • Safety — no exceptions in ISRs; deterministic failure modes.

Key points

  • Embedded C++ uses a restricted feature set focused on classes, RAII, and compile-time safety.
  • Exceptions and RTTI are usually disabled; error codes and RAII replace them.
  • Heap and STL containers are limited; static and pool allocation dominate on MCU.
  • Know your flags and style guide — C++ power requires discipline on small targets.