Effective Modern C++ 笔记(番外)- 智能指针手撕(面试简化版)
注:本文由大模型辅助生成
本文将深入探讨 std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
的核心机制,并提供一套可以在面试中手写的、简化的 C++ 实现。
这套实现将专注于核心逻辑,以清晰地展示你对这些智能指针工作原理的理解。
在 C++ 的世界里,内存管理曾经是一场混乱的战争,充满了内存泄漏(忘记 delete
)和悬空指针(delete
后继续使用)的陷阱。智能指针的出现,如同三位大将,为这场战争带来了秩序和纪律,它们的核心是 RAII (Resource Acquisition Is Initialization) 原则——资源的生命周期与对象的生命周期绑定。
unique_ptr
—— 独占天下的霸主
核心思想:独占所有权 (Exclusive Ownership)
unique_ptr
如同一位霸主,它宣称:“这个资源,普天之下,唯我一人所有”。它不允许任何形式的分享或复制,确保在任何时刻,只有一个指针能管理该资源。
深入讲解:
- 轻量级:它是一个零成本抽象。在没有自定义删除器的情况下,
sizeof(std::unique_ptr<T>)
与 sizeof(T*)
完全相同。它本质上只是一个带有析构函数的裸指针。
- 移动语义:它的“霸道”体现在禁止拷贝上。你不能复制一个
unique_ptr
,因为那会产生两个所有者。但你可以转让(移动)所有权,就像禅让王位一样。一旦所有权被移走,原来的 unique_ptr
就变为空指针 (nullptr
)。
- RAII:当
unique_ptr
本身被销毁时(例如离开作用域),它的析构函数会自动被调用,从而释放它所拥有的资源(默认调用 delete
)。
面试手写实现 MyUniquePtr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <iostream> #include <utility>
template<typename T> class MyUniquePtr { private: T* ptr_;
public: explicit MyUniquePtr(T* ptr = nullptr) noexcept : ptr_(ptr) {}
~MyUniquePtr() noexcept { if (ptr_) { delete ptr_; } }
MyUniquePtr(const MyUniquePtr& other) = delete; MyUniquePtr& operator=(const MyUniquePtr& other) = delete;
MyUniquePtr(MyUniquePtr&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept { if (this != &other) { if (ptr_) { delete ptr_; } ptr_ = other.ptr_; other.ptr_ = nullptr; } return *this; }
T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } T* get() const { return ptr_; } explicit operator bool() const { return ptr_ != nullptr; }
T* release() noexcept { T* temp = ptr_; ptr_ = nullptr; return temp; }
void reset(T* ptr = nullptr) noexcept { if (ptr_) { delete ptr_; } ptr_ = ptr; } };
template<typename T, typename... Args> MyUniquePtr<T> make_my_unique(Args&&... args) { return MyUniquePtr<T>(new T(std::forward<Args>(args)...)); }
|
shared_ptr
—— 联合执政的元老
核心思想:共享所有权 (Shared Ownership)
shared_ptr
是一位善于合作的元老,它允许:“这个资源,大家都可以用,我们共同管理”。它通过一个精巧的“引用计数”机制来跟踪资源的所有者数量。
深入讲解:
- 引用计数:这是
shared_ptr
的灵魂。与资源一同存在的还有一个控制块 (Control Block),它至少包含一个强引用计数。
- 每当一个
shared_ptr
被拷贝(或新创建一个指向同一资源的 shared_ptr
),强引用计数 +1
。
- 每当一个
shared_ptr
被销毁或指向别处,强引用计数 -1
。
- 当强引用计数减到
0
时,资源被销毁。
- 开销:
shared_ptr
不是免费的。
- 内存开销:一个
shared_ptr
对象通常是裸指针的两倍大,因为它需要一个指针指向资源,另一个指针指向控制块。
- 性能开销:引用计数的增减必须是原子操作,以保证多线程安全。原子操作比普通整数操作要慢。
- 控制块:除了强引用计数,控制块还存储了弱引用计数(为
weak_ptr
服务)和自定义删除器等信息。
面试手写实现 MySharedPtr
这个实现相对复杂,关键在于分离控制块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| #include <atomic>
struct ControlBlock { std::atomic<int> strong_count; std::atomic<int> weak_count;
ControlBlock() : strong_count(1), weak_count(0) {} virtual ~ControlBlock() = default; virtual void destroy_resource() = 0; };
template<typename T> struct ControlBlockImpl : public ControlBlock { T* resource_ptr;
ControlBlockImpl(T* ptr) : resource_ptr(ptr) {} void destroy_resource() override { delete resource_ptr; } };
template<typename T> class MyWeakPtr;
template<typename T> class MySharedPtr { private: T* ptr_; ControlBlock* control_block_; friend class MyWeakPtr<T>;
void cleanup() { if (control_block_) { if (--control_block_->strong_count == 0) { control_block_->destroy_resource(); if (control_block_->weak_count == 0) { delete control_block_; } } } }
public: explicit MySharedPtr(T* ptr = nullptr) : ptr_(ptr) { if (ptr) { control_block_ = new ControlBlockImpl<T>(ptr); } else { control_block_ = nullptr; } }
~MySharedPtr() { cleanup(); }
MySharedPtr(const MySharedPtr& other) noexcept : ptr_(other.ptr_), control_block_(other.control_block_) { if (control_block_) { ++control_block_->strong_count; } }
MySharedPtr& operator=(const MySharedPtr& other) noexcept { if (this != &other) { cleanup(); ptr_ = other.ptr_; control_block_ = other.control_block_; if (control_block_) { ++control_block_->strong_count; } } return *this; } MySharedPtr(MySharedPtr&& other) noexcept : ptr_(other.ptr_), control_block_(other.control_block_) { other.ptr_ = nullptr; other.control_block_ = nullptr; }
MySharedPtr& operator=(MySharedPtr&& other) noexcept { if (this != &other) { cleanup(); ptr_ = other.ptr_; control_block_ = other.control_block_; other.ptr_ = nullptr; other.control_block_ = nullptr; } return *this; }
T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } T* get() const { return ptr_; } int use_count() const { return control_block_ ? control_block_->strong_count.load() : 0; } };
template<typename T, typename... Args> MySharedPtr<T> make_my_shared(Args&&... args) { return MySharedPtr<T>(new T(std::forward<Args>(args)...)); }
|
weak_ptr
—— 洞察全局的谋士
核心思想:非拥有型观察者 (Non-owning Observer)
weak_ptr
是一位谋士,它不带兵(不拥有资源),也不参与执政(不增加引用计数)。它只是静静地观察着 shared_ptr
管理的资源,并能告诉你这个资源“是否还健在”。
深入讲解:
- 解决循环引用:这是
weak_ptr
最重要的使命。当两个对象通过 shared_ptr
相互引用时,会形成一个引用环,它们的引用计数永远无法归零,导致内存泄漏。将其中一方的引用改为 weak_ptr
,即可打破这个环。
- 不增加强引用:
weak_ptr
从一个 shared_ptr
创建,它会共享控制块,并增加弱引用计数,但强引用计数不变。
- 安全访问:你不能直接访问
weak_ptr
指向的资源。必须先调用 lock()
方法。
lock()
会检查强引用计数。如果 > 0,它会返回一个指向该资源的有效的 shared_ptr
(同时强引用计数+1)。
- 如果 == 0,说明资源已被销毁,
lock()
返回一个空的 shared_ptr
。
这种“先检查后锁定”的机制保证了访问的绝对安全。
面试手写实现 MyWeakPtr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| template<typename T> class MyWeakPtr { private: T* ptr_; ControlBlock* control_block_;
public: MyWeakPtr() noexcept : ptr_(nullptr), control_block_(nullptr) {}
MyWeakPtr(const MySharedPtr<T>& sp) noexcept : ptr_(sp.ptr_), control_block_(sp.control_block_) { if (control_block_) { ++control_block_->weak_count; } }
MyWeakPtr(const MyWeakPtr& other) noexcept : ptr_(other.ptr_), control_block_(other.control_block_) { if (control_block_) { ++control_block_->weak_count; } }
MyWeakPtr& operator=(const MyWeakPtr& other) noexcept { if (this != &other) { cleanup(); ptr_ = other.ptr_; control_block_ = other.control_block_; if (control_block_) { ++control_block_->weak_count; } } return *this; }
~MyWeakPtr() { cleanup(); } MySharedPtr<T> lock() const { if (expired()) { return MySharedPtr<T>(nullptr); } MySharedPtr<T> sp; sp.ptr_ = ptr_; sp.control_block_ = control_block_; ++control_block_->strong_count; return sp; }
bool expired() const { return !control_block_ || control_block_->strong_count == 0; } private: void cleanup() { if (control_block_) { if (--control_block_->weak_count == 0 && control_block_->strong_count == 0) { delete control_block_; } } } };
|
总结与演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| struct MyData { MyData() { std::cout << "MyData created\n"; } ~MyData() { std::cout << "MyData destroyed\n"; } void greet() { std::cout << "Hello from MyData\n"; } };
struct Node { ~Node() { std::cout << "Node destroyed\n"; } MyWeakPtr<Node> next; };
void test_unique() { std::cout << "\n--- Testing MyUniquePtr ---\n"; auto u_ptr = make_my_unique<MyData>(); u_ptr->greet(); }
void test_shared() { std::cout << "\n--- Testing MySharedPtr ---\n"; MySharedPtr<MyData> s_ptr1; { auto s_ptr2 = make_my_shared<MyData>(); s_ptr1 = s_ptr2; std::cout << "Use count: " << s_ptr1.use_count() << std::endl; } std::cout << "Use count: " << s_ptr1.use_count() << std::endl; }
void test_weak_and_cycle() { std::cout << "\n--- Testing MyWeakPtr for cycles ---\n"; auto n1 = make_my_shared<Node>(); auto n2 = make_my_shared<Node>(); n1->next = n2; n2->next = n1; std::cout << "n1 use count: " << n1.use_count() << std::endl; std::cout << "n2 use count: " << n2.use_count() << std::endl; }
int main() { test_unique(); test_shared(); test_weak_and_cycle(); return 0; }
|
这套代码虽然简化了许多细节(如对数组的支持 T[]
,自定义删除器,make_shared
的单次分配优化),但它完整地、正确地实现了三种智能指针的核心机制,足以在面试中展示你对现代 C++ 资源管理的深刻理解。