Giao diện
🎯 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
| Type | Heap Allocation | Use in Embedded |
|---|---|---|
std::unique_ptr | Optional | ✅ Sometimes OK |
std::shared_ptr | Yes + overhead | ❌ Avoid |
std::weak_ptr | Requires shared | ❌ Avoid |
| Raw pointers | No | ✅ 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>();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
✅ 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};
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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!1
2
3
4
5
6
2
3
4
5
6
✅ 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);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
✅ 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
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
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!1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
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! │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
📚 Tổng kết
| Situation | Use |
|---|---|
| Hardware registers | Raw volatile* |
| Static globals | No pointer / raw |
| Stack references | Raw pointer |
| RAII resources | unique_ptr + custom deleter |
| Shared ownership | Redesign 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.