如何在 C++ 中实现自定义智能指针?

在 C++ 里,智能指针是管理动态内存的重要工具。最常用的标准库智能指针包括 std::unique_ptrstd::shared_ptrstd::weak_ptr。若要自己实现一个简化版的智能指针,既可以加深对内存管理的理解,也能根据具体项目需求进行定制。下面给出一个基于引用计数的简易 SharedPtr 实现,并说明关键细节与可扩展点。

1. 设计目标

  • 自动销毁:当最后一个指针失效时,资源被释放。
  • 线程安全:可在多线程环境下使用。
  • 轻量级:内部数据结构尽量小巧,避免频繁拷贝。
  • 可自定义删除器:支持非 delete 的资源清理方式。

2. 基础实现思路

核心思想是使用一个共享的控制块(ControlBlock)来存放:

  • 对象指针 T* ptr
  • 原子引用计数 `std::atomic ref_count`
  • 可选的自定义删除器 std::function<void(T*)> deleter

`SharedPtr

` 只持有指向该控制块的原始指针,所有复制、移动操作都更新引用计数。 ### 3. 代码实现 “`cpp #include #include #include template class SharedPtr { private: struct ControlBlock { T* ptr; std::atomic ref_count; std::function deleter; ControlBlock(T* p, std::function d = nullptr) : ptr(p), ref_count(1), deleter(d) {} }; ControlBlock* cb; // 指向控制块 void release() { if (cb && –cb->ref_count == 0) { if (cb->deleter) cb->deleter(cb->ptr); else delete cb->ptr; delete cb; } } public: // 构造 explicit SharedPtr(T* p = nullptr, std::function d = nullptr) : cb(p ? new ControlBlock(p, d) : nullptr) {} // 复制构造 SharedPtr(const SharedPtr& other) : cb(other.cb) { if (cb) ++cb->ref_count; } // 移动构造 SharedPtr(SharedPtr&& other) noexcept : cb(other.cb) { other.cb = nullptr; } // 赋值 SharedPtr& operator=(const SharedPtr& other) { if (this != &other) { release(); cb = other.cb; if (cb) ++cb->ref_count; } return *this; } SharedPtr& operator=(SharedPtr&& other) noexcept { if (this != &other) { release(); cb = other.cb; other.cb = nullptr; } return *this; } // 访问 T& operator*() const { return *(cb->ptr); } T* operator->() const { return cb->ptr; } T* get() const { return cb ? cb->ptr : nullptr; } // 查询引用计数 size_t use_count() const { return cb ? cb->ref_count.load() : 0; } bool unique() const { return use_count() == 1; } // 解除引用 void reset(T* p = nullptr, std::function d = nullptr) { release(); if (p) cb = new ControlBlock(p, d); else cb = nullptr; } // 析构 ~SharedPtr() { release(); } }; “` ### 4. 关键细节说明 1. **原子引用计数** 使用 `std::atomic ` 保证多线程环境下的安全。`++cb->ref_count` 与 `–cb->ref_count` 都是原子操作。 2. **自定义删除器** `deleter` 允许用户传入 lambda 或函数对象来实现特殊资源释放逻辑(如 `fopen` 的文件句柄、`malloc` 的内存块等)。 3. **异常安全** 在 `release` 中先尝试减少计数,若计数为 0 才执行删除。若 `deleter` 抛异常,整个 `release` 也会抛异常,但因为是销毁阶段,标准建议使用 `noexcept` 或在删除器中捕获异常。 4. **拷贝与移动语义** – 复制构造/赋值时,引用计数递增。 – 移动构造/赋值时,原对象失去控制块的所有权,避免多余计数操作。 5. **弱引用** 若需要实现 `WeakPtr`,只需在控制块中再加一个原子 `weak_count`,并提供相应的锁定逻辑。此处已不再展开。 ### 5. 使用示例 “`cpp int main() { SharedPtr p1(new int(42)); std::cout p2 = p1; // 复制 std::cout p3 = std::move(p2); // 移动 std::cout file(fopen(“log.txt”, “w”), [](FILE* fp){ if(fp) fclose(fp); }); file->fprintf(“Hello, world!\n”); return 0; } “` ### 6. 性能与优化 – **控制块分配**:将 `ControlBlock` 与对象一起分配(如 `std::allocate_shared`)可减少堆分配次数,提升缓存局部性。 – **轻量化**:如果线程安全不是必要条件,可使用非原子计数,进一步简化。 – **自定义内存分配器**:为控制块提供自定义 allocator,减少内存碎片。 ### 7. 小结 实现一个自定义 `SharedPtr` 需要关注引用计数、线程安全、删除器以及异常安全。上述实现已具备基本功能,可直接用于教学或轻量级项目。根据具体需求,可进一步扩展到弱引用、数组管理、原始对象生命周期管理等高级特性。祝编码愉快!

发表评论