raw_ptr + delete 易致 double-free 或悬垂指针,因手动管理易漏删、多删或误用;RAII 仅保单作用域自动释放,共享所有权需 shared_ptr 等计数指针;手写 ref_ptr 需独立计数器、正确拷贝/移动/析构逻辑,并注意 reset/reset(nullptr) 等细节。

为什么 raw_ptr + delete 容易出 double-free 或 dangling pointer
裸指针手动管理生命周期,本质是把“谁该释放”“何时释放”的决策权交给程序员。一旦对象被多个模块共享,或异常中途跳出作用域,delete 就可能被漏调、多调,或者在对象已销毁后继续解引用。RAII 本身不解决共享所有权问题,它只保证单个作用域内资源自动释放——而计数指针(如 std::shared_ptr)才是对 RAII 的自然延伸:把“引用计数”这个状态也纳入资源管理范畴。
常见错误现象包括:
- 函数返回局部
new出的对象指针,调用方忘记delete→ 内存泄漏 - 两个
std::shared_ptr指向同一块原始内存但非同源构造(比如都用new构造)→ 计数错乱,double-free - 循环引用(A 持有 B 的
std::shared_ptr,B 也持有 A 的)→ 计数永不归零,对象无法析构
如何手写一个最小可用的 ref_ptr(非线程安全版)
核心是三要素:指向对象的原始指针、引用计数器(堆上独立分配)、析构逻辑封装在析构函数中。不能把计数器放在对象内部(侵入式),否则无法通用;也不能和对象共用一块内存(除非定制 operator new),否则 delete 时无法安全释放计数器。
实操建议:
- 计数器必须动态分配:
new size_t(1),确保生命周期独立于托管对象 - 拷贝构造/赋值时,先对原指针的计数器执行
--(*cnt),再检查是否为 0 决定是否delete托管对象和计数器本身 - 移动语义要显式置空右值的指针和计数器,避免后续析构误删
- 提供
get()和operator->等接口,行为需与std::shared_ptr一致
简化示例(关键路径):
template<typename t>
class ref_ptr {
T* ptr_;
size_t* cnt_;
<p>public:
explicit ref<em>ptr(T* p = nullptr) : ptr</em>(p), cnt_(p ? new size_t(1) : nullptr) {}</p>
<pre class="brush:php;toolbar:false;">ref_ptr(const ref_ptr& other) : ptr_(other.ptr_), cnt_(other.cnt_) {
if (cnt_) ++(*cnt_);
}
~ref_ptr() {
if (cnt_ && --(*cnt_) == 0) {
delete ptr_;
delete cnt_;
}
}
T* get() const { return ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
};
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
ref_ptr 和 std::shared_ptr 在 reset / release 行为上的关键差异
标准库的 std::shared_ptr::reset() 会先减少旧引用计数,再接管新对象;而手写版本若没实现类似逻辑,直接 ptr_ = new_obj; cnt_ = new_cnt; 就会跳过旧资源清理,导致泄漏或悬垂。
容易踩的坑:
- 没实现
reset(),靠赋值替代 → 可能触发不必要的拷贝构造+析构,性能差且计数逻辑变重 - 实现
reset(nullptr)时忘记置空cnt_,导致后续析构仍尝试delete cnt_(野指针) -
release()不应存在:计数指针语义上不支持“交出所有权但不减计数”,那会破坏 RAII 契约 - 不支持自定义删除器(
Deleter),无法处理malloc分配或需要CloseHandle的资源
循环引用真的只能靠 std::weak_ptr 解?手写怎么加
弱引用不是“另一个指针”,而是对同一计数器的**非拥有式观察者**。它不参与 cnt_ 的增减,只在需要时尝试“升级”为强引用(即检查对象是否还活着)。所以必须额外维护一个“弱计数”字段,记录有多少 weak_ptr 正在观察该计数器。
实操要点:
- 计数器结构需扩展为两个字段:
strong_count和weak_count -
weak_ptr析构时只减weak_count;仅当strong_count == 0 && weak_count == 0才真正释放计数器内存 -
lock()方法需原子读取strong_count并比较是否 > 0,再构造新的ref_ptr(此时才增strong_count) - 所有操作必须考虑竞态:即使非线程安全版,也要明确标注“不保证多线程并发调用安全”,否则使用者可能误用于异步场景
这已经逼近 std::shared_ptr 实现复杂度的底线——多数项目直接用标准库更稳。自己实现,通常只为理解原理或嵌入受限环境(无 STL),而非替代。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










