跳转至

嵌入式C++教程——ETL(Embedded Template Library)

好奇心:为什么在嵌入式世界里,总有人把 new 当成"危险品"而随身带手套?答案很简单:堆是不可预测的。ETL(Embedded Template Library)就是为了解决这个问题而生:把熟悉的容器/算法思想搬到嵌入式场景,但把动态分配剔除掉,让一切变得可预测、可度量、可审计。


ETL 是啥

ETL 是一个面向嵌入式的 C++ 模板库(作者/维护在 GitHub 上,采用 MIT 许可证),它提供了许多类似 STL 的容器与工具,但所有容器都是固定容量最大容量的 —— 不会调用 malloc/new,因此非常适合对内存可控性有严格要求的系统。它兼容较老的编译器和嵌入式工具链(设计目标覆盖 C++03 及更高版本的环境)。


为什么用 ETL

你可以把 ETL 看成一个"没有动态分配的 STL 副本":它保留了熟悉的 API 风格(对程序员友好),同时把内存以静态/栈/预分配块的形式提前分配好——这带来两个直接好处:

  • 确定性:不再担心 heap 碎片、分配失败或无法预测的延迟。
  • 性能与缓存友好:容器的存储通常是连续分配的,遍历时更亲近 CPU 缓存。

总之:你想要 STL 的便捷,但又要裸机级的可预测性,ETL 很合适。


核心特性

ETL 的设计重点可浓缩为几句话:固定/最大容量容器(向量、队列、链表、map 等变体)、无堆分配、与 STL 风格 API 兼容(尽量)、MIT 开源并在 GitHub 活跃维护。你可以在 Arduino / PlatformIO / 各种嵌入式生态里找到它的移植或封装。


快速上手

下面是一个最小示例:固定容量的向量(注意:实际头文件名与命名空间以你用的 ETL 版本为准):

#include <etl/vector.h>
#include <iostream>

int main() {
    // 最大容量 8 的静态向量,内存事先分配好(无动态分配)
    etl::vector<int, 8> v;

    for (int i = 0; i < 6; ++i) {
        v.push_back(i * 10); // 如果超过容量,会有 safe variant 报错或返回错误(取决于配置)
    }

    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << "\n";
    }

    // 指定位置插入/删除等 API 很像 STL
    v.insert(v.begin() + 2, 99);
}

提示:ETL 的容器通常以模板参数指定容量(或用 etl::pool 类别的方式管理对象池),所以在编译期就能知道内存占用。


ETL 的局限

ETL 并不打算替代 STL 在所有场景的地位——它是"为嵌入式量身定做的替代/补充"。如果你项目中对象来自第三方库且无法改造,或者你确实需要动态增长到不确定的大小,STL(或堆)仍然是更方便的选择。ETL 的静态分配还可能导致二进制体积增加(模板实例化),需要权衡编译时的代码膨胀问题

所以 ETL 不是魔法,而是工程上的折衷 —— 它用模板把"静态内存 + 熟悉 API"这两个愿望合二为一。对于需要确定性、低内存开销、对实时性有要求的系统(比如 bootloader、RTOS 任务队列、驱动层 buffer 管理),ETL 是一把好刀。拿它来修理日常的内存问题,往往比带着 malloc 在板子上做瑜伽要稳妥得多

查看完整可编译示例 > 注意:以下示例演示了 ETL 库的使用模式,但为了能独立编译,使用了模拟的 ETL 接口。实际使用时需要包含真正的 ETL 头文件。
// etl_vector_demo.cpp - ETL 固定容量向量示例
// 注意:此示例需要 ETL 库支持
// https://github.com/ETLCPP/etl

#include <iostream>

// 如果你有 ETL 库,取消下面的注释并调整路径
// #include <etl/vector.h>

// 模拟 ETL vector 接口(用于演示)
template<typename T, size_t N>
class EtlVectorDemo {
    T buffer_[N];
    size_t size_ = 0;

public:
    bool push_back(const T& value) {
        if (size_ >= N) return false;
        buffer_[size_++] = value;
        return true;
    }

    size_t size() const { return size_; }
    size_t capacity() const { return N; }

    T& operator[](size_t index) { return buffer_[index]; }
    const T& operator[](size_t index) const { return buffer_[index]; }

    T* data() { return buffer_; }
    const T* data() const { return buffer_; }

    // 迭代器支持
    T* begin() { return buffer_; }
    T* end() { return buffer_ + size_; }
    const T* begin() const { return buffer_; }
    const T* end() const { return buffer_ + size_; }

    void insert(size_t pos, const T& value) {
        if (size_ >= N || pos > size_) return;
        for (size_t i = size_; i > pos; --i) {
            buffer_[i] = buffer_[i - 1];
        }
        buffer_[pos] = value;
        ++size_;
    }
};

int main() {
    std::cout << "=== ETL Vector Demo ===\n\n";

    // 最大容量 8 的静态向量,内存事先分配好(无动态分配)
    // etl::vector<int, 8> v;  // 使用真实 ETL 时的写法
    EtlVectorDemo<int, 8> v;

    std::cout << "Vector capacity: " << v.capacity() << '\n';

    std::cout << "\n=== Adding elements ===\n";
    for (int i = 0; i < 6; ++i) {
        if (v.push_back(i * 10)) {
            std::cout << "Added: " << i * 10 << ", size: " << v.size() << '\n';
        }
    }

    std::cout << "\n=== Contents ===\n";
    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << '\n';

    std::cout << "\n=== Insert at position 2 ===\n";
    v.insert(2, 99);
    for (const auto& val : v) {
        std::cout << val << ' ';
    }
    std::cout << '\n';

    std::cout << "\n=== Filling to capacity ===\n";
    for (int i = 0; i < 10; ++i) {
        if (!v.push_back(i * 100)) {
            std::cout << "Failed to add " << i * 100 << " (buffer full)\n";
            break;
        }
    }

    std::cout << "\nFinal size: " << v.size() << '/' << v.capacity() << '\n';

    std::cout << "\n=== Key benefits ===\n";
    std::cout << "- No heap allocation\n";
    std::cout << "- Deterministic memory usage\n";
    std::cout << "- Compile-time capacity specification\n";
    std::cout << "- STL-compatible interface\n";

    return 0;
}
查看更多示例:队列、对象池
// etl_queue_demo.cpp - ETL 固定容量队列示例
// 注意:此示例需要 ETL 库支持
// https://github.com/ETLCPP/etl

#include <iostream>
#include <array>

// 模拟 ETL queue 接口(用于演示)
template<typename T, size_t N>
class EtlQueueDemo {
    std::array<T, N + 1> buffer_;  // +1 for empty/full distinction
    size_t head_ = 0;
    size_t tail_ = 0;

public:
    bool push(const T& value) {
        if (full()) return false;
        buffer_[head_] = value;
        head_ = (head_ + 1) % (N + 1);
        return true;
    }

    bool pop(T& out) {
        if (empty()) return false;
        out = buffer_[tail_];
        tail_ = (tail_ + 1) % (N + 1);
        return true;
    }

    bool empty() const { return head_ == tail_; }
    bool full() const { return (head_ + 1) % (N + 1) == tail_; }

    size_t size() const {
        if (head_ >= tail_) return head_ - tail_;
        return (N + 1) - (tail_ - head_);
    }

    size_t capacity() const { return N; }
};

// 消息结构
struct Message {
    int id;
    int data;
};

int main() {
    std::cout << "=== ETL Queue Demo ===\n\n";

    // 消息队列,容量 8
    // etl::queue<Message, 8> msg_queue;  // 使用真实 ETL 时的写法
    EtlQueueDemo<Message, 8> msg_queue;

    std::cout << "Queue capacity: " << msg_queue.capacity() << "\n\n";

    // 生产者:发送消息
    std::cout << "=== Producer: Sending messages ===\n";
    for (int i = 0; i < 10; ++i) {
        Message msg{i, i * 100};
        if (msg_queue.push(msg)) {
            std::cout << "Sent: ID=" << msg.id << ", Data=" << msg.data << '\n';
        } else {
            std::cout << "Queue full! Message " << i << " dropped.\n";
            break;
        }
    }

    std::cout << "\nQueue size: " << msg_queue.size() << "\n\n";

    // 消费者:处理消息
    std::cout << "=== Consumer: Processing messages ===\n";
    while (!msg_queue.empty()) {
        Message msg;
        if (msg_queue.pop(msg)) {
            std::cout << "Processed: ID=" << msg.id << ", Data=" << msg.data << '\n';
        }
    }

    std::cout << "\nQueue empty: " << (msg_queue.empty() ? "yes" : "no") << '\n';

    std::cout << "\n=== Typical embedded use case ===\n";
    std::cout << "- ISR pushes data to queue\n";
    std::cout << "- Main loop processes at its own pace\n";
    std::cout << "- No malloc, deterministic behavior\n";
    std::cout << "- Type-safe, unlike raw byte buffers\n";

    return 0;
}
// etl_pool_demo.cpp - ETL 对象池示例
// 注意:此示例需要 ETL 库支持
// https://github.com/ETLCPP/etl

#include <iostream>
#include <cstddef>

// 模拟 ETL object_pool 接口(用于演示)
template<typename T, size_t N>
class EtlPoolDemo {
    struct Node {
        Node* next;
        alignas(T) char storage[sizeof(T)];
    };

    std::array<Node, N> pool_;
    Node* free_list_;

public:
    EtlPoolDemo() {
        // 初始化空闲链表
        free_list_ = &pool_[0];
        for (size_t i = 0; i < N - 1; ++i) {
            pool_[i].next = &pool_[i + 1];
        }
        pool_[N - 1].next = nullptr;
    }

    // 分配对象(调用构造函数)
    template<typename... Args>
    T* create(Args&&... args) {
        if (!free_list_) return nullptr;

        Node* node = free_list_;
        free_list_ = free_list_->next;

        return new (node->storage) T(std::forward<Args>(args)...);
    }

    // 释放对象(调用析构函数)
    void destroy(T* obj) {
        if (!obj) return;

        obj->~T();

        Node* node = reinterpret_cast<Node*>(obj);
        node->next = free_list_;
        free_list_ = node;
    }

    size_t capacity() const { return N; }
};

// 示例对象
struct Sensor {
    int id;
    const char* name;
    int value;

    Sensor(int i, const char* n) : id(i), name(n), value(0) {}

    void read() {
        value = id * 42;  // 模拟读取
    }

    void display() const {
        std::cout << "  Sensor " << id << " (" << name
                  << "): " << value << '\n';
    }
};

int main() {
    std::cout << "=== ETL Object Pool Demo ===\n\n";

    // 创建容量为 5 的传感器对象池
    // etl::object_pool<Sensor, 5> sensor_pool;  // 使用真实 ETL 时的写法
    EtlPoolDemo<Sensor, 5> sensor_pool;

    std::cout << "Pool capacity: " << sensor_pool.capacity() << "\n\n";

    // 分配传感器对象
    std::cout << "=== Creating sensors ===\n";
    Sensor* s1 = sensor_pool.create(1, "Temperature");
    Sensor* s2 = sensor_pool.create(2, "Humidity");
    Sensor* s3 = sensor_pool.create(3, "Pressure");
    Sensor* s4 = sensor_pool.create(4, "Light");
    Sensor* s5 = sensor_pool.create(5, "Motion");

    // 尝试分配第 6 个(应该失败)
    Sensor* s6 = sensor_pool.create(6, "Sound");
    if (!s6) {
        std::cout << "Failed to create Sensor 6 (pool exhausted)\n";
    }

    // 使用传感器
    std::cout << "\n=== Reading sensors ===\n";
    s1->read();
    s2->read();
    s3->read();
    s4->read();
    s5->read();

    std::cout << "\n=== Sensor values ===\n";
    s1->display();
    s2->display();
    s3->display();
    s4->display();
    s5->display();

    // 释放一些传感器
    std::cout << "\n=== Releasing some sensors ===\n";
    sensor_pool.destroy(s2);
    sensor_pool.destroy(s4);
    std::cout << "Released Sensors 2 and 4\n";

    // 现在可以重新分配
    std::cout << "\n=== Creating new sensors ===\n";
    Sensor* s7 = sensor_pool.create(7, "Voltage");
    if (s7) {
        s7->read();
        s7->display();
    }

    Sensor* s8 = sensor_pool.create(8, "Current");
    if (s8) {
        s8->read();
        s8->display();
    }

    // 清理
    std::cout << "\n=== Cleanup ===\n";
    sensor_pool.destroy(s1);
    sensor_pool.destroy(s3);
    sensor_pool.destroy(s5);
    sensor_pool.destroy(s7);
    sensor_pool.destroy(s8);

    std::cout << "\n=== Key benefits ===\n";
    std::cout << "- Pre-allocated memory, no fragmentation\n";
    std::cout << "- O(1) allocation and deallocation\n";
    std::cout << "- Objects constructed on-demand\n";
    std::cout << "- Perfect for fixed-size object collections\n";

    return 0;
}