sidebar_position: 3
Embedded Template Library,STL 库在嵌入式领域的一个补充,纯头文件库,并且尽量避免了继承来减少虚函数开销,官网
ETL 大概需要 20-50KB,STM32 空间参考:
建议版本:C++11
按规则存放元素的东西,支持常见容器,如: vector, map, bitset, …
虽然很多都支持动态内存,如 vector_ext。但在大多数情况下,不建议使用
最常见的容器,大小: (SIZE * sizeof(T)) + (2 * sizeof(size_t)) + sizeof(T*)
#include
void vectorTest () {
etl::vector<int, 10> base; // 初始化时需要声明大小, 所以push不会导致迭代器实现
int *ptr = base.begin(); // 迭代器就是指针
etl::vector<int, 10>::reverse_iterator rPtr = base.rbegin(); // 反向迭代器是真迭代器
base.push_back(27); // STL方法
bool isFull = base.full(); // 额外方法
int *newInt = new int [5]{67, 89, 36};
memcpy(ptr + 1, newInt, 5 * sizeof(int));
base.repair(); // 通过底层函数移动元素后,必须使用该方法
}
对象池可以减少从头创建每个对象的性能开销
#include
void poolTest () {
class Obj {
public:
Obj () { std::cout << "Obj ctor" << std::endl; }
~Obj () { std::cout << "Obj dtor" << std::endl; }
};
etl::pool<Obj, 10> objPool;
Obj *newObj1 = objPool.create(); // 分配一个初始化好的对象
Obj *newObj2 = objPool.allocate(); // 未初始化的裸指针
objPool.destroy(newObj1); // 析构对象后放回池子
objPool.release(newObj2); // 放回池子
}
用于管理一系列 bit 位
#include
void bitsetTest () {
// 全特化模板: <8, uint8_t>, <16, uint16_t>, <32, uint32_t>, <8, uint64_t>
etl::bitset<8, uint8_t> base{"0101"}; // <数量, 基本存储单位>
bool result = base.any(0x3f); // 掩码, 仅使用低六位
auto spanize = base.span(); // 返回span对象
}
表示一个可能存在的值,比使用特殊值更为安全
#include
etl::optional<int> optionalTest () {
etl::optional<int> returnValue{42};
returnValue = {}; // 不同于 stl, 这里将调用默认构造
return returnValue;
}
auto result = optionalTest();
if (result.has_value())
std::cout << result.value() << std::endl;
else
std::cout << "INOP" << std::endl;
既有基本的 string 类型
#include
void stringTest () {
etl::string<10> str;
str.repair(); // 同 vector.repair()
size_t location = str.find(""); // STL方法
bool isStart = str.starts_with(""); // 额外方法
}
也有很好用的其他类型转 string,当然也可以使用 string_stream
#include
#include
void toStringTest () {
etl::format_spec format;
format.hex().width(8).fill('0'); // 格式要求: 16进制, 宽度8, 左填充0
etl::string<10> str;
etl::to_string(401, str, format);
}
或者 string 转数值
#include
void fromStringTest () {
char cStr[]{"101"};
etl::string<10> cppStr{"101"};
int result = etl::to_arithmetic<int>(cStr, 3, etl::bin); // 以二进制形式转换
result = etl::to_arithmetic<int>(cppStr, etl::radix::binary); // 以二进制形式转换
}
还有一些好用的小功能
void otherStringTest () {
etl::string<32> str{"a,b:c"};
etl::trim_whitespace_left(str); // 删除左侧空白 (' ', '\t', '\n', '\r', '\f', '\v')
etl::reverse(str); // 翻转字符串
etl::vector<etl::string<32>, 10> base;
etl::optional<etl::string_view> token;
while ((token = etl::get_token(str, ".,:", token, true)))
base.emplace_back(token.value()); // 实现类似 python的split()
}
聪明人总结出的方案
ETL 实现了全部或部分细节:观察者、责任链、单例、访问者
一个对象发生变化时,通知其他对象。注意虚函数带来的性能开销
#include
struct Position {
int x, y;
};
struct Velocity {
double x;
};
typedef etl::observer<const Position&, Velocity> Observer; // 对象: 两个结构体
class Handler : public Observer { // 订阅消息, 纯虚函数, 需要实现全部方法
public:
void notification (const Position &pos) override {
std::cout << pos.x << " " << pos.y << std::endl;
}
void notification (const Velocity v) override {
std::cout << v.x << std::endl;
}
};
class Driver : public etl::observable<Observer, 1> { // 发布消息
public:
void event () {
Position pos{1, 2};
notify_observers(pos);
}
};
void observeTest () { // 发布消息后, 订阅者查收
Driver driver;
Handler handler;
driver.add_observer(handler);
driver.event();
}
不能算设计模式吧,但这里的重载比一般情况要灵活一些
#include
int result_int;
float result_float;
auto overloaded = etl::make_overload([](int i) { result_int = i; },
[](float i) { result_float = i; });
template <typename T, typename TOverload>
void Function (T value, TOverload &&ol) {
ol(value);
}
void overloadTest () {
Function(1, overloaded);
}
一系列算数和图像处理算法
一种计算移动平均的简化方法
#include "etl/pseudo_moving_average.h"
#include "etl/vector.h"
void pmaTest () {
etl::vector<int, 10> data{1, 2, 3, 5};
etl::pseudo_moving_average<int, 5, 2> base(0); // 样本数, 缩放因子, (初始值)
std::copy(data.begin(), data.end(), base.input()); // 通过 copy() 添加一串
base.add(27); // 通过 add() 方法添加一个值
std::cout << base.value();
}
hash 支持嵌入式中常用算法:fnv、murmur3、jenkins
crc 支持非常全面,实现了绝大部分能想到的 crc 类型
#include
void hashTest () {
etl::murmur3<uint32_t> hasher;
std::string sentense{"abcdefg"};
hasher.add(sentense.begin(), sentense.end()); // 只支持 add() 方法
uint32_t result = hasher.value();
}
#include
void crcTest () {
std::string sentense{"abcdef"};
etl::crc32_bzip2 crc;
crc.add(sentense.begin(), sentense.end()); // 既可以通过 add() 方法
std::copy(sentense.begin(), sentense.end(), crc.input()); // 也能通过 copy() 实现
uint32_t result = crc.value();
}
比较大的设计,例如:委托、状态机、多任务
有限个状态间转换
看了会代码,感觉用不上,就这样吧
#include
const etl::message_router_id_t CONTROL = 0;
// 事件
struct EventId {
enum { START, STOP };
};
class Start : public etl::message<EventId::START> {};
class Stop : public etl::message<EventId::STOP> {};
// 状态
struct StateId {
enum { IDLE, RUNNING, NUMBER_OF_STATES };
};
class Control : public etl::fsm {
public:
Control (): fsm(CONTROL) {}
};
// Idle状态, 接受启动事件
class Idle : public etl::fsm_state<Control, Idle, StateId::IDLE, Start> {
public:
etl::fsm_state_id_t on_event (const Start &event) {
return StateId::RUNNING;
}
etl::fsm_state_id_t on_event_unknown (const etl::imessage &img) {
return STATE_ID;
}
};
// Running状态, 接受停止事件
class Running : public etl::fsm_state<Control, Running, StateId::RUNNING, Stop> {
public:
etl::fsm_state_id_t on_event (const Stop &event) {
return StateId::IDLE;
}
etl::fsm_state_id_t on_event_unknown (const etl::imessage &img) {
return STATE_ID;
}
};
Idle idle;
Running running;
etl::ifsm_state *stateList[StateId::NUMBER_OF_STATES] = {&idle, &running};
void fsmTest () {
Control control;
control.set_states(stateList, etl::size(stateList));
control.receive(Start()); // 接受一个启动事件, 转换为Running状态
}