Embedded C++

Modern C++ brings type safety and zero-cost abstractions to microcontrollers. Learn which features to use (classes, namespaces, references, constexpr, templates, enum class, RAII, std::array) and which to avoid or control (exceptions, RTTI, dynamic allocation, iostreams), plus virtual-function costs and compile-time peripheral configuration.

C++ is increasingly used in embedded products (Arduino, mbed, Zephyr C++ apps, automotive AUTOSAR Adaptive). Used carefully, it produces code as small and fast as C while catching more errors at compile time.

An analogy

Embedded C++ is like a professional toolbox where you leave the heavy power tools (exceptions, heap) in the van and use the precise hand tools (constexpr, templates) that add no weight.

Why C++?

Stronger typing (enum class, references), encapsulation (classes, namespaces), generic code (templates), compile-time computation (constexpr), and automatic cleanup (RAII) — with no runtime overhead when used carefully.

Features to restrict

Exceptions and RTTI add code size and unpredictable timing (compile with -fno-exceptions -fno-rtti). Avoid heap allocation (new/delete, std::vector growth) in real-time code; use std::array, static pools and placement new. Avoid iostream (huge).

Polymorphism

Virtual functions cost a vtable pointer per object and an indirect call — acceptable for drivers with multiple implementations. Static polymorphism (templates, CRTP) removes the indirection when types are known at compile time.

Standards and practice

MISRA C++ and AUTOSAR C++14 rules for safety-critical code; Embedded Template Library (ETL) for fixed-capacity containers; C++17/20 features (constexpr if, concepts, std::span) improve clarity.

Formulas

Worked example: Virtual function overhead

Given: 100 sensor objects with virtual read(); 32-bit MCU

Find: RAM overhead.

Answer: 400 bytes RAM + one vtable per class in flash

Worked example: constexpr table

Given: Sine table of 256 entries computed with constexpr

Find: Runtime cost.

Answer: Zero runtime computation, no RAM used

Type-safe GPIO with templates and RAII

#include <cstdint>

enum class Port : std::uintptr_t { A = 0x40020000, B = 0x40020400 };

template <Port P, unsigned Pin>
struct Gpio {
    static_assert(Pin < 16, "pin out of range");
    static constexpr std::uintptr_t base = static_cast<std::uintptr_t>(P);
    static volatile std::uint32_t& reg(unsigned off) {
        return *reinterpret_cast<volatile std::uint32_t*>(base + off);
    }
    static void set()    { reg(0x18) = 1u << Pin; }          // BSRR set
    static void clear()  { reg(0x18) = 1u << (Pin + 16); }   // BSRR reset
};

using Led = Gpio<Port::A, 5>;          // resolved entirely at compile time

class CriticalSection {                // RAII: interrupts restored automatically
public:
    CriticalSection()  { __asm volatile("cpsid i"); }
    ~CriticalSection() { __asm volatile("cpsie i"); }
};

void blink_once() {
    CriticalSection cs;                // disabled here...
    Led::set();
    Led::clear();
}                                      // ...re-enabled when cs goes out of scope

Template parameters fix the port and pin at compile time, so Led::set() compiles to a single store instruction. static_assert catches wrong pins before the code ever runs; RAII makes it impossible to forget re-enabling interrupts.

Common mistakes

Applications

Key takeaways

Going further