嵌入式环境下的智能指针:定制化 unique_ptr 和 shared_ptr
大家好,今天我们来聊聊在嵌入式环境下如何实现自定义的智能指针,特别是 unique_ptr 和 shared_ptr。嵌入式系统通常资源受限,标准库提供的智能指针可能因为内存占用、性能开销等原因不太适用。因此,根据实际需求定制化智能指针,可以更好地满足嵌入式环境的要求。
1. 嵌入式环境下智能指针的需求与挑战
嵌入式系统对资源有着严格的限制。内存通常较小,CPU 性能也相对较弱。在这种环境下使用标准库的 std::unique_ptr 和 std::shared_ptr 会遇到以下挑战:
- 内存占用: 标准库的
shared_ptr需要维护一个引用计数器,通常分配在堆上,增加了内存开销。在内存受限的嵌入式系统中,堆内存的分配和释放需要谨慎管理。 - 性能开销: 引用计数的增加和减少操作,特别是在多线程环境下,需要进行原子操作,这会带来额外的性能开销。
- 异常处理: 某些嵌入式系统可能禁用异常处理,而标准库的智能指针在构造和析构时可能会抛出异常。
- 代码体积: 标准库的实现通常比较复杂,代码体积较大,这对于 Flash 空间有限的嵌入式系统来说也是一个问题。
因此,我们需要针对嵌入式环境的特点,定制化智能指针,在功能、性能和资源占用之间找到平衡。
2. 自定义 unique_ptr
unique_ptr 用于独占资源的所有权,一个资源只能被一个 unique_ptr 管理。它在资源管理方面非常高效,因为它不需要维护引用计数。
2.1 基本实现
以下是一个简单的 unique_ptr 的实现:
template <typename T>
class unique_ptr {
private:
T* ptr;
public:
// 构造函数
unique_ptr(T* p = nullptr) : ptr(p) {}
// 移动构造函数
unique_ptr(unique_ptr&& other) : ptr(other.ptr) {
other.ptr = nullptr;
}
// 赋值运算符 (移动赋值)
unique_ptr& operator=(unique_ptr&& other) {
if (this != &other) {
reset(other.ptr);
other.ptr = nullptr;
}
return *this;
}
// 析构函数
~unique_ptr() {
delete ptr;
}
// 解引用运算符
T& operator*() const {
return *ptr;
}
// 箭头运算符
T* operator->() const {
return ptr;
}
// 获取原始指针
T* get() const {
return ptr;
}
// 释放所有权,返回原始指针
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
// 重置指针
void reset(T* p = nullptr) {
delete ptr;
ptr = p;
}
// 禁用拷贝构造函数和赋值运算符
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
2.2 定制化删除器
unique_ptr 可以使用自定义的删除器,这对于管理非 new 分配的资源非常有用。例如,可以使用自定义的删除器来释放通过 malloc 分配的内存,或者关闭文件句柄。
template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
T* ptr;
Deleter deleter;
public:
// 构造函数
unique_ptr(T* p = nullptr, const Deleter& d = Deleter()) : ptr(p), deleter(d) {}
// 移动构造函数
unique_ptr(unique_ptr&& other) : ptr(other.ptr), deleter(std::move(other.deleter)) {
other.ptr = nullptr;
}
// 赋值运算符 (移动赋值)
unique_ptr& operator=(unique_ptr&& other) {
if (this != &other) {
reset(other.ptr);
deleter = std::move(other.deleter); // 重要:移动deleter
other.ptr = nullptr;
}
return *this;
}
// 析构函数
~unique_ptr() {
deleter(ptr);
}
// 解引用运算符
T& operator*() const {
return *ptr;
}
// 箭头运算符
T* operator->() const {
return ptr;
}
// 获取原始指针
T* get() const {
return ptr;
}
// 释放所有权,返回原始指针
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
// 重置指针
void reset(T* p = nullptr) {
deleter(ptr);
ptr = p;
}
// 禁用拷贝构造函数和赋值运算符
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
示例:使用 malloc 和 free
struct MallocDeleter {
void operator()(void* p) const {
free(p);
}
};
int main() {
int* data = (int*)malloc(sizeof(int));
unique_ptr<int, MallocDeleter> ptr(data);
*ptr = 42;
return 0;
}
2.3 嵌入式环境下的优化
- 静态分配删除器: 如果删除器不依赖于运行时状态,可以将其声明为
static成员函数或全局函数,避免在unique_ptr对象中存储删除器对象。 - 禁用异常: 确保代码不抛出异常,或者使用
noexcept说明符来标记构造函数、析构函数和赋值运算符。 - 减少代码体积: 避免使用复杂的模板元编程技巧,尽量使用简单的代码实现。
3. 自定义 shared_ptr
shared_ptr 用于共享资源的所有权,多个 shared_ptr 可以指向同一个资源。当最后一个 shared_ptr 销毁时,资源才会被释放。
3.1 基本实现 (单线程)
以下是一个简单的 shared_ptr 的单线程实现:
template <typename T>
class shared_ptr {
private:
T* ptr;
size_t* count; // 引用计数
public:
// 构造函数
shared_ptr(T* p = nullptr) : ptr(p), count(p ? new size_t(1) : nullptr) {}
// 拷贝构造函数
shared_ptr(const shared_ptr& other) : ptr(other.ptr), count(other.count) {
if (count) {
(*count)++;
}
}
// 赋值运算符 (拷贝赋值)
shared_ptr& operator=(const shared_ptr& other) {
if (this != &other) {
// 减少当前对象的引用计数
if (count && --(*count) == 0) {
delete ptr;
delete count;
}
// 增加新对象的引用计数
ptr = other.ptr;
count = other.count;
if (count) {
(*count)++;
}
}
return *this;
}
// 析构函数
~shared_ptr() {
if (count && --(*count) == 0) {
delete ptr;
delete count;
}
}
// 解引用运算符
T& operator*() const {
return *ptr;
}
// 箭头运算符
T* operator->() const {
return ptr;
}
// 获取原始指针
T* get() const {
return ptr;
}
// 获取引用计数
size_t use_count() const {
return count ? *count : 0;
}
};
3.2 多线程支持
在多线程环境下,引用计数的增加和减少操作需要是原子操作,以避免数据竞争。可以使用 std::atomic 来实现原子计数。
#include <atomic>
template <typename T>
class shared_ptr {
private:
T* ptr;
std::atomic<size_t>* count; // 原子引用计数
public:
// 构造函数
shared_ptr(T* p = nullptr) : ptr(p), count(p ? new std::atomic<size_t>(1) : nullptr) {}
// 拷贝构造函数
shared_ptr(const shared_ptr& other) : ptr(other.ptr), count(other.count) {
if (count) {
count->fetch_add(1, std::memory_order_relaxed); // 增加引用计数
}
}
// 赋值运算符 (拷贝赋值)
shared_ptr& operator=(const shared_ptr& other) {
if (this != &other) {
// 减少当前对象的引用计数
if (count && count->fetch_sub(1, std::memory_order_release) == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
delete ptr;
delete count;
}
// 增加新对象的引用计数
ptr = other.ptr;
count = other.count;
if (count) {
count->fetch_add(1, std::memory_order_relaxed); // 增加引用计数
}
}
return *this;
}
// 析构函数
~shared_ptr() {
if (count && count->fetch_sub(1, std::memory_order_release) == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
delete ptr;
delete count;
}
}
// 解引用运算符
T& operator*() const {
return *ptr;
}
// 箭头运算符
T* operator->() const {
return ptr;
}
// 获取原始指针
T* get() const {
return ptr;
}
// 获取引用计数
size_t use_count() const {
return count ? count->load(std::memory_order_relaxed) : 0;
}
};
3.3 嵌入式环境下的优化
- 避免动态内存分配: 可以考虑使用预分配的内存池来存储引用计数。
- 使用轻量级原子操作: 某些嵌入式平台可能提供更轻量级的原子操作,可以替代
std::atomic。例如,可以使用无锁算法来实现引用计数。 - 限制最大引用计数: 可以限制
shared_ptr的最大引用计数,以减少计数器的存储空间。 - 考虑使用 intrusive_ptr:
intrusive_ptr将引用计数嵌入到被管理的对象中,可以避免额外的内存分配。 但是需要修改被管理对象的结构。
3.4 intrusive_ptr 的实现
template <typename T>
class intrusive_ptr {
private:
T* ptr;
public:
// 构造函数
intrusive_ptr(T* p = nullptr) : ptr(p) {
if (ptr) {
intrusive_ptr_add_ref(ptr);
}
}
// 拷贝构造函数
intrusive_ptr(const intrusive_ptr& other) : ptr(other.ptr) {
if (ptr) {
intrusive_ptr_add_ref(ptr);
}
}
// 赋值运算符
intrusive_ptr& operator=(const intrusive_ptr& other) {
if (this != &other) {
intrusive_ptr_release(ptr);
ptr = other.ptr;
if (ptr) {
intrusive_ptr_add_ref(ptr);
}
}
return *this;
}
// 析构函数
~intrusive_ptr() {
intrusive_ptr_release(ptr);
}
// 解引用运算符
T& operator*() const {
return *ptr;
}
// 箭头运算符
T* operator->() const {
return ptr;
}
// 获取原始指针
T* get() const {
return ptr;
}
};
// 需要在被管理的对象中提供以下两个函数
void intrusive_ptr_add_ref(MyObject* p);
void intrusive_ptr_release(MyObject* p);
// 示例
class MyObject {
public:
std::atomic<int> ref_count;
MyObject() : ref_count(0) {}
~MyObject() {
// 在对象销毁时执行清理操作
}
};
void intrusive_ptr_add_ref(MyObject* p) {
p->ref_count.fetch_add(1, std::memory_order_relaxed);
}
void intrusive_ptr_release(MyObject* p) {
if (p && p->ref_count.fetch_sub(1, std::memory_order_release) == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
delete p;
}
}
int main() {
MyObject* obj = new MyObject();
intrusive_ptr<MyObject> ptr1(obj);
intrusive_ptr<MyObject> ptr2(ptr1);
return 0;
}
4. 总结表格
| 特性 | std::unique_ptr |
自定义 unique_ptr |
std::shared_ptr |
自定义 shared_ptr |
intrusive_ptr |
|---|---|---|---|---|---|
| 所有权 | 独占 | 独占 | 共享 | 共享 | 共享 |
| 引用计数 | 无 | 无 | 有 | 有 | 有 (对象内) |
| 内存占用 | 较小 | 较小 | 较大 | 可优化 | 较小 |
| 性能 | 高 | 高 | 较低 | 可优化 | 高 |
| 异常安全 | 较高 | 需保证 | 较高 | 需保证 | 需保证 |
| 适用场景 | 独占资源管理 | 独占资源管理 | 共享资源管理 | 共享资源管理 | 共享资源管理 |
| 嵌入式环境优化 | 使用自定义删除器 | 静态分配删除器,禁用异常 | 预分配内存池,轻量级原子操作 | 预分配内存池,轻量级原子操作 | 嵌入引用计数 |
| 代码复杂性 | 中等 | 较低 | 中等 | 可优化 | 中等 |
5. 总结:定制化智能指针,平衡资源与功能
在嵌入式环境下,标准库的智能指针可能并不总是最佳选择。通过定制化 unique_ptr 和 shared_ptr,可以更好地适应资源限制,提高性能,并确保代码的可靠性。 关键在于理解嵌入式环境的特点,并根据实际需求进行优化。
通过对删除器,内存分配方式和线程同步策略的调整,我们可以实现更高效、更可靠的智能指针,从而提升嵌入式系统的整体性能。
考虑使用 intrusive_ptr 在特定场景下可以减少内存分配的开销,但是需要对目标对象进行修改。
更多IT精英技术系列讲座,到智猿学院