Skip to content

🎯 Smart Pointers in Embedded

Smart pointers có chỗ trong embedded — nhưng không phải lúc nào cũng phù hợp!

Smart Pointers Overview

TypeHeap AllocationUse in Embedded
std::unique_ptrOptional✅ Sometimes OK
std::shared_ptrYes + overhead❌ Avoid
std::weak_ptrRequires shared❌ Avoid
Raw pointersNo✅ Often best

When unique_ptr Works

Compile-time Known Size

cpp
#include <memory>
#include <array>

// ✅ OK: No heap allocation - compile-time size
class SensorManager {
    // Static buffer, unique_ptr just for RAII semantics
    std::array<uint8_t, 256> buffer_;
    
public:
    void process() { /* ... */ }
};

// ✅ OK: Global/static allocation
std::unique_ptr<SensorManager> gSensorManager;

void init() {
    // Allocated once at startup, never freed
    gSensorManager = std::make_unique<SensorManager>();
}

Custom Deleters (No Heap)

cpp
// Resource handle with RAII, no heap allocation
struct FileHandle {
    int fd;
};

void closeFile(FileHandle* handle) {
    if (handle && handle->fd >= 0) {
        close(handle->fd);
    }
}

// unique_ptr for RAII, custom deleter
std::unique_ptr<FileHandle, decltype(&closeFile)> 
openFile(const char* path) {
    FileHandle* handle = new FileHandle{open(path, O_RDONLY)};
    return {handle, closeFile};
}

When to Use Raw Pointers

Memory-Mapped Registers

cpp
// ✅ Raw pointers are REQUIRED for hardware addresses
volatile uint32_t* const GPIOA_DATA = 
    (volatile uint32_t*)0x40020014;

// ❌ unique_ptr CANNOT point to hardware addresses
// std::unique_ptr<uint32_t> gpio{(uint32_t*)0x40020014}; // WRONG!

Non-Owning References

cpp
class Driver {
    // Raw pointer: doesn't own the hardware, just references it
    volatile GPIO_Registers* gpio_;  // ✅ Raw pointer
    
public:
    explicit Driver(volatile GPIO_Registers* gpio) 
        : gpio_(gpio) {}
    
    void toggle(uint8_t pin) {
        gpio_->DATA ^= (1 << pin);
    }
};

// Hardware is at fixed address - never "deleted"
GPIO_Registers* const GPIOA = (GPIO_Registers*)0x40020000;
Driver ledDriver(GPIOA);

Static/Stack Objects

cpp
// Object on stack - no smart pointer needed
void process() {
    DataBuffer buffer;  // Stack allocated
    
    // Pass pointer to function (non-owning)
    processData(&buffer);  // Raw pointer OK
}

// Static global - never freed
DataLogger gLogger;

void logData() {
    gLogger.log("event");  // No pointer needed at all
}

Why Avoid shared_ptr in Embedded?

cpp
// ❌ shared_ptr overhead:
// - Control block allocation (16-24 bytes per object)
// - Atomic reference counting (performance cost)
// - Thread-safe operations (may disable interrupts)

#include <memory>

// This creates HEAP allocation + control block
auto ptr = std::make_shared<Sensor>();

// In embedded with 2KB RAM, this is wasteful!

Ownership Guidelines

┌─────────────────────────────────────────────────────────────────┐
│                    POINTER OWNERSHIP IN EMBEDDED                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   SCENARIO                          RECOMMENDATION              │
│   ────────                          ──────────────              │
│                                                                 │
│   Hardware register                 Raw volatile pointer        │
│   Static/global object              No pointer (or raw)         │
│   Stack object reference            Raw pointer                 │
│   RAII resource (file, handle)      unique_ptr + custom deleter │
│   Transfer ownership once           unique_ptr (if heap OK)     │
│   Shared ownership                  ❌ Redesign!                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Practical Pattern: Static Allocation

cpp
// Instead of dynamic allocation, use placement new
#include <new>

// Pre-allocated static buffer
alignas(Sensor) uint8_t sensorBuffer[sizeof(Sensor)];
Sensor* gSensor = nullptr;

void initSensor() {
    // Construct in pre-allocated buffer
    gSensor = new (sensorBuffer) Sensor();
}

void deinitSensor() {
    if (gSensor) {
        gSensor->~Sensor();  // Call destructor explicitly
        gSensor = nullptr;
    }
}

RAII Without Heap

cpp
// Lock guard pattern - no heap allocation
class InterruptLock {
    bool wasEnabled_;
    
public:
    InterruptLock() {
        wasEnabled_ = areInterruptsEnabled();
        disableInterrupts();
    }
    
    ~InterruptLock() {
        if (wasEnabled_) {
            enableInterrupts();
        }
    }
    
    // Non-copyable
    InterruptLock(const InterruptLock&) = delete;
    InterruptLock& operator=(const InterruptLock&) = delete;
};

void criticalOperation() {
    InterruptLock lock;  // Stack allocated RAII
    
    // Interrupts disabled here
    modifySharedData();
    
    // lock destructor re-enables interrupts
}

📚 Tổng kết

SituationUse
Hardware registersRaw volatile*
Static globalsNo pointer / raw
Stack referencesRaw pointer
RAII resourcesunique_ptr + custom deleter
Shared ownershipRedesign architecture!

➡️ Tiếp theo

Hardware có thể ngắt chương trình bất kỳ lúc nào để xử lý events...

Interrupts → — ISR basics và best practices.