
1. 智能指针概述C 智能指针本质上是RAIIResource Acquisition Is Initialization思想在指针管理上的应用。它们通过将裸指针封装在对象中自动在合适的时机析构时释放所管理的资源从而大幅降低内存泄漏、悬空指针等风险。标准库提供的主要智能指针有std::unique_ptr—— 独占所有权不可拷贝只可移动。std::shared_ptr—— 共享所有权基于引用计数当计数归零时释放资源。std::weak_ptr—— 弱引用不增加引用计数用于打破循环引用或观察资源。2. 智能指针常见问题2.1 基础理解什么是 RAII智能指针如何体现 RAIIRAII 让资源生命周期与对象生命周期绑定构造时获取资源析构时释放资源。智能指针在构造时接管指针析构时delete或调用删除器。std::unique_ptr为什么不能拷贝如何转移所有权拷贝构造函数和拷贝赋值函数被 delete只能通过移动语义 (std::move) 转移所有权。std::shared_ptr的引用计数如何工作控制块control block存储强引用计数、弱引用计数、删除器等。每次拷贝、赋值增加强计数析构减少当强计数归零时释放资源弱计数也归零时释放控制块。std::weak_ptr的作用是什么它与shared_ptr有什么关系weak_ptr可从shared_ptr构造不增加强引用计数可通过lock()返回一个有效的shared_ptr若资源仍存在否则返回空。主要用于打破循环引用或实现缓存、观察者模式。什么时候用make_shared和make_unique有什么优势异常安全避免在new和shared_ptr构造之间插入其他操作可能抛异常导致内存泄漏。内存分配效率make_shared一次性分配对象和控制块减少一次堆分配缓存更友好。make_unique同理虽然控制块分离但也简化代码与异常安全。2.2 使用陷阱循环引用是如何产生的如何解决两个对象各自持有对方的shared_ptr导致引用计数永远不会归零。解决方法将其中一个改为weak_ptr。#include iostream #include memory class B; // 前置声明 class A { public: std::shared_ptrB b_ptr; ~A() { std::cout A destroyed std::endl; } }; class B { public: std::shared_ptrA a_ptr; // 这里如果改成 weak_ptr 就不会循环引用 ~B() { std::cout B destroyed std::endl; } }; int main() { { auto a std::make_sharedA(); auto b std::make_sharedB(); a-b_ptr b; // a 指向 b b-a_ptr a; // b 指向 a // 离开作用域a 和 b 局部 shared_ptr 销毁引用计数各减 1 } std::cout 离开作用域但 A 和 B 都没有被销毁循环引用 std::endl; return 0; }从同一个裸指针构造两个不同的shared_ptr会发生什么会分别创建独立的控制块导致双重删除undefined behavior。应始终通过已有的shared_ptr拷贝或使用make_shared。在构造函数中调用shared_from_this()为什么会出错enable_shared_from_this需要已存在一个对应的shared_ptr控制块。如果对象还未被shared_ptr管理会抛bad_weak_ptr异常或未定义行为。应先创建shared_ptr之后再安全使用shared_from_this()。为什么需要自定义删除器举例说明。管理的资源不是用new分配的如FILE*、socket、内存池分配的内存等或者需要在释放前执行特定清理动作。unique_ptr的删除器是类型的一部分shared_ptr则是控制块中类型擦除。shared_ptr的线程安全性如何引用计数的增减是原子操作因此多个线程同时拷贝/析构同一个shared_ptr的不同实例是安全的。同一个shared_ptr对象被多线程读写如赋值则不是线程安全的需要外部同步。被管理的对象访问需要用户自行保证线程安全。2.3 实现与原理如何实现一个简易的unique_ptr见后续手搓代码。如何实现一个简易的shared_ptr控制块需要什么控制块需包含强引用计数、弱引用计数如需支持weak_ptr和删除器。需要考虑线程安全时使用std::atomic。enable_shared_from_this的实现原理是什么基类内部通常存储一个weak_ptr在对象被shared_ptr管理时由shared_ptr的构造函数检测并设置。调用shared_from_this()时通过该weak_ptr的lock()返回shared_ptr。shared_ptr的 aliasing constructor 是干什么的它允许shared_ptr管理一个对象但指向该对象的某个子对象生命周期共享原对象的引用计数。常用于指向结构体成员如struct Bar { int x; }; auto p std::make_sharedBar(); std::shared_ptrint q(p, p-x);2.4 性能与内存shared_ptr的控制块占用多少内存make_shared与new创建的控制块位置有什么不同控制块通常包含两个原子计数、删除器、分配器等一般 24~32 字节。new shared_ptrT(new T)T 和控制块分离两次堆分配。make_sharedT()一次分配同时容纳 T 和控制块内存局部性更好但weak_ptr可能延长大块内存的释放。3. 手搓智能指针实现3.1 简化版UniquePtrtemplatetypename T class UniquePtr { public: // 默认构造 UniquePtr() noexcept : ptr_(nullptr) {} // 从裸指针构造 explicit UniquePtr(T* p) noexcept : ptr_(p) {} // 禁止拷贝 UniquePtr(const UniquePtr) delete; UniquePtr operator(const UniquePtr) delete; // 移动构造 UniquePtr(UniquePtr other) noexcept : ptr_(other.ptr_) { other.ptr_ nullptr; } // 移动赋值 UniquePtr operator(UniquePtr other) noexcept { if (this ! other) { reset(other.release()); } return *this; } ~UniquePtr() { reset(); } // 观察 T* get() const noexcept { return ptr_; } T operator*() const noexcept { return *ptr_; } T* operator-() const noexcept { return ptr_; } explicit operator bool() const noexcept { return ptr_ ! nullptr; } // 释放所有权 T* release() noexcept { T* tmp ptr_; ptr_ nullptr; return tmp; } // 重置为新指针 void reset(T* p nullptr) noexcept { T* old ptr_; ptr_ p; if (old) { delete old; } } void swap(UniquePtr other) noexcept { std::swap(ptr_, other.ptr_); } private: T* ptr_; }; // 数组特化省略类似加 delete[] 即可要点禁止拷贝只移动析构时deleterelease只放弃所有权不释放reset释放旧资源并接管新资源。3.2 简化版SharedPtr暂不支持WeakPtr#include atomic // 控制块 struct ControlBlock { std::atomiclong ref_count{1}; // 强引用计数 virtual void dispose() 0; // 释放资源 virtual ~ControlBlock() default; }; // 带对象的控制块 templatetypename T struct ControlBlockImpl : ControlBlock { T* ptr; explicit ControlBlockImpl(T* p) : ptr(p) {} void dispose() override { delete ptr; // 这里不 delete this因为还有弱引用可能在用这里只负责释放资源 // 在我们的简化版中直接由 SharedPtr 析构处理控制块的释放 } }; templatetypename T class SharedPtr { public: SharedPtr() noexcept : ptr_(nullptr), ctrl_(nullptr) {} explicit SharedPtr(T* p) : ptr_(p), ctrl_(p ? new ControlBlockImplT(p) : nullptr) {} // 拷贝构造 SharedPtr(const SharedPtr other) noexcept : ptr_(other.ptr_), ctrl_(other.ctrl_) { if (ctrl_) ctrl_-ref_count.fetch_add(1, std::memory_order_relaxed); } // 移动构造 SharedPtr(SharedPtr other) noexcept : ptr_(other.ptr_), ctrl_(other.ctrl_) { other.ptr_ nullptr; other.ctrl_ nullptr; } // 拷贝赋值 SharedPtr operator(const SharedPtr other) noexcept { if (this ! other) { release(); ptr_ other.ptr_; ctrl_ other.ctrl_; if (ctrl_) ctrl_-ref_count.fetch_add(1, std::memory_order_relaxed); } return *this; } // 移动赋值 SharedPtr operator(SharedPtr other) noexcept { if (this ! other) { release(); ptr_ other.ptr_; ctrl_ other.ctrl_; other.ptr_ nullptr; other.ctrl_ nullptr; } return *this; } ~SharedPtr() { release(); } T* get() const noexcept { return ptr_; } T operator*() const noexcept { return *ptr_; } T* operator-() const noexcept { return ptr_; } long use_count() const noexcept { return ctrl_ ? ctrl_-ref_count.load(std::memory_order_relaxed) : 0; } explicit operator bool() const noexcept { return ptr_ ! nullptr; } void reset() { release(); ptr_ nullptr; ctrl_ nullptr; } void reset(T* p) { release(); ptr_ p; ctrl_ p ? new ControlBlockImplT(p) : nullptr; } private: void release() { if (ctrl_ ctrl_-ref_count.fetch_sub(1, std::memory_order_acq_rel) 1) { // 最后一个强引用 ctrl_-dispose(); delete ctrl_; // 如果没有弱引用可以直接删除控制块 } ptr_ nullptr; ctrl_ nullptr; } T* ptr_; ControlBlock* ctrl_; };说明这个版本支持多线程安全的引用计数std::atomic但还没有支持WeakPtr。如果加入弱引用控制块需要分离为强计数和弱计数并在强弱计数都归零时才删除控制块本身。3.3 支持WeakPtr的SharedPtr和WeakPtrstruct ControlBlockW { std::atomiclong strong_count{1}; // 强引用计数 std::atomiclong weak_count{1}; // 弱引用计数包括控制块自身 virtual void dispose() 0; virtual void destroy_control_block() 0; virtual ~ControlBlockW() default; }; templatetypename T struct ControlBlockImplW : ControlBlockW { T* ptr; explicit ControlBlockImplW(T* p) : ptr(p) {} void dispose() override { delete ptr; } void destroy_control_block() override { delete this; // 由 weak 计数归零调用 } }; templatetypename T class WeakPtr; templatetypename T class SharedPtrW { friend class WeakPtrT; public: SharedPtrW() noexcept : ptr_(nullptr), ctrl_(nullptr) {} explicit SharedPtrW(T* p) : ptr_(p), ctrl_(p ? new ControlBlockImplWT(p) : nullptr) {} SharedPtrW(const SharedPtrW other) noexcept : ptr_(other.ptr_), ctrl_(other.ctrl_) { add_ref(); } SharedPtrW(SharedPtrW other) noexcept : ptr_(other.ptr_), ctrl_(other.ctrl_) { other.ptr_ nullptr; other.ctrl_ nullptr; } SharedPtrW operator(const SharedPtrW other) noexcept { if (this ! other) { release(); ptr_ other.ptr_; ctrl_ other.ctrl_; add_ref(); } return *this; } SharedPtrW operator(SharedPtrW other) noexcept { if (this ! other) { release(); ptr_ other.ptr_; ctrl_ other.ctrl_; other.ptr_ nullptr; other.ctrl_ nullptr; } return *this; } ~SharedPtrW() { release(); } T* get() const noexcept { return ptr_; } T operator*() const noexcept { return *ptr_; } T* operator-() const noexcept { return ptr_; } long use_count() const noexcept { return ctrl_ ? ctrl_-strong_count.load(std::memory_order_relaxed) : 0; } // 供 WeakPtr 使用的“共享构造” explicit SharedPtrW(T* ptr, ControlBlockW* ctrl) noexcept : ptr_(ptr), ctrl_(ctrl) { add_ref(); } private: void add_ref() { if (ctrl_) ctrl_-strong_count.fetch_add(1, std::memory_order_relaxed); } void release() { if (ctrl_) { if (ctrl_-strong_count.fetch_sub(1, std::memory_order_acq_rel) 1) { ctrl_-dispose(); // 强引用为0减少弱引用控制块自己的一次弱引用 if (ctrl_-weak_count.fetch_sub(1, std::memory_order_acq_rel) 1) { ctrl_-destroy_control_block(); } } } ptr_ nullptr; ctrl_ nullptr; } T* ptr_ nullptr; ControlBlockW* ctrl_ nullptr; }; templatetypename T class WeakPtr { public: WeakPtr() noexcept : ptr_(nullptr), ctrl_(nullptr) {} WeakPtr(const SharedPtrWT sp) noexcept : ptr_(sp.ptr_), ctrl_(sp.ctrl_) { if (ctrl_) ctrl_-weak_count.fetch_add(1, std::memory_order_relaxed); } WeakPtr(const WeakPtr other) noexcept : ptr_(other.ptr_), ctrl_(other.ctrl_) { if (ctrl_) ctrl_-weak_count.fetch_add(1, std::memory_order_relaxed); } WeakPtr(WeakPtr other) noexcept : ptr_(other.ptr_), ctrl_(other.ctrl_) { other.ptr_ nullptr; other.ctrl_ nullptr; } WeakPtr operator(const WeakPtr other) noexcept { if (this ! other) { release(); ptr_ other.ptr_; ctrl_ other.ctrl_; if (ctrl_) ctrl_-weak_count.fetch_add(1, std::memory_order_relaxed); } return *this; } WeakPtr operator(const SharedPtrWT sp) noexcept { release(); ptr_ sp.ptr_; ctrl_ sp.ctrl_; if (ctrl_) ctrl_-weak_count.fetch_add(1, std::memory_order_relaxed); return *this; } ~WeakPtr() { release(); } SharedPtrWT lock() const { if (!ctrl_) return SharedPtrWT(); // 尝试提升强计数必须保证强计数0 long old ctrl_-strong_count.load(std::memory_order_relaxed); while (old 0) { if (ctrl_-strong_count.compare_exchange_weak(old, old 1, std::memory_order_acquire, std::memory_order_relaxed)) { return SharedPtrWT(ptr_, ctrl_); // SharedPtrW的私有构造函数增加引用 } } return SharedPtrWT(); } long use_count() const noexcept { return ctrl_ ? ctrl_-strong_count.load(std::memory_order_relaxed) : 0; } bool expired() const noexcept { return use_count() 0; } private: void release() { if (ctrl_) { if (ctrl_-weak_count.fetch_sub(1, std::memory_order_acq_rel) 1) { ctrl_-destroy_control_block(); } } ptr_ nullptr; ctrl_ nullptr; } T* ptr_ nullptr; ControlBlockW* ctrl_ nullptr; };关键点控制块强计数代表shared_ptr实例数量弱计数代表weak_ptr实例数量 1控制块自身。当强计数归零时调用dispose释放对象然后递减弱计数。当弱计数也归零时销毁控制块。WeakPtr::lock()原子地尝试将强计数 1若强计数 0 则成功否则返回空。3.4 带删除器的UniquePtr与SharedPtr类型擦除UniquePtr的删除器作为模板参数或利用无捕获 lambdaC20 支持无捕获 lambda 用于模板参数推导否则常用std::function但会有额外开销实际标准库是用模板。这里展示一种使用std::function的简化版templatetypename T class UniquePtrWithDeleter { public: using Deleter std::functionvoid(T*); UniquePtrWithDeleter() noexcept : ptr_(nullptr) {} explicit UniquePtrWithDeleter(T* p, Deleter d [](T* ptr){ delete ptr; }) : ptr_(p), deleter_(std::move(d)) {} ~UniquePtrWithDeleter() { reset(); } // 禁止拷贝支持移动 ... void reset(T* p nullptr, Deleter d {}) { if (ptr_) deleter_(ptr_); ptr_ p; deleter_ std::move(d); } private: T* ptr_; Deleter deleter_; };对于SharedPtr由于控制块通过虚函数dispose()实现多态删除器的类型被擦除。可以在控制块中存储删除器templatetypename T, typename Deleter struct ControlBlockWithDeleter : ControlBlockW { T* ptr; Deleter deleter; ControlBlockWithDeleter(T* p, Deleter d) : ptr(p), deleter(std::move(d)) {} void dispose() override { deleter(ptr); } void destroy_control_block() override { delete this; } };这样SharedPtr的构造就可以接受任意删除器。4. 发散知识点4.1 RAII 与异常安全智能指针保证了即使在异常抛出时栈展开也会正确调用析构函数释放资源。使用裸指针 new/delete极易在异常路径下遗漏释放。4.2enable_shared_from_this基类内部持有一个weak_ptr。当shared_ptr首次管理该对象时会检测是否继承自enable_shared_from_this并设置内部的弱引用。实现原理大致是在shared_ptr构造函数中通过__enable_shared_from_this_helper调用_M_assign将控制块指针传入。示例手搓极简templatetypename T class EnableSharedFromThis { public: SharedPtrWT shared_from_this() { return weak_this_.lock(); } SharedPtrWconst T shared_from_this() const { return weak_this_.lock(); } private: mutable WeakPtrT weak_this_; templatetypename U friend class SharedPtrW; };4.3 内存序与线程安全上述实现中std::memory_order_acq_rel用于原子操作确保释放资源前所有线程对资源的访问可见。weak_ptr::lock中的 CAS 使用memory_order_acquire保证如果成功获取强引用能看到之前强引用持有者所做的修改。4.4shared_ptr的 aliasing constructor可用于管理一个对象的生命周期但指向其成员。手搓实现中需要单独存储“指向的指针”和“控制块对应的生命周期指针”// aliasing constructor SharedPtrW(const SharedPtrW owner, T* ptr) noexcept : ptr_(ptr), ctrl_(owner.ctrl_) { add_ref(); }此时get()返回ptr_但生命周期受控于owner的原始对象。4.5 与 C 风格 API 的交互从 C API 获取裸指针并用unique_ptr接管自定义删除器如unique_ptrchar, decltype(free)。将指针传递给 C 函数时可用get()获得裸指针但需保证智能指针生命周期覆盖使用期。4.6 性能陷阱不宜无必要地“按值传递shared_ptr”增加原子操作开销。weak_ptr::lock()有 CAS 循环代价较高。循环引用检测没有自动机制需要设计层面避免。4.7 智能指针与多态shared_ptrT可以通过shared_ptrBase持有派生类对象通过隐式转换和模板构造函数因为shared_ptr的构造函数是模板的内部保存T*派生类指针控制块销毁时正确调用派生类析构函数通过delete ptr或虚析构函数或删除器中绑定派生类类型。4.8 析构顺序与静态对象若全局或静态对象使用shared_ptr需要注意静态析构顺序问题。可用局部静态shared_ptr的get()或weak_ptr延长生命周期等技巧。