cpp八股2
实现三个智能指针
首先是 UniquePtr,它是独占式的智能指针,不能被复制,只能被移动。
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
| template<typename T> class UniquePtr { public: UniquePtr(T* p = nullptr) : 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) return *this;
delete ptr; ptr = other.ptr; other.ptr = nullptr; return *this; } ~UniquePtr() { delete ptr; } T* get() const { return ptr; } T& operator*() const { return *ptr; } T* operator->() const { return ptr; } T* release() { T* tmp = ptr; ptr = nullptr; return tmp; } void reset(T* p = nullptr) { delete ptr; ptr = p; } operator bool() const { return ptr != nullptr; } private: T* ptr; };
|
在C++03中引入了一个 std::auto_ptr,它的思想也是将资源独占。但是它的实现内允许使用复制构造函数和复制赋值运算符,这会导致一些问题,例如在函数传参时会发生资源的转移,导致原来的指针变成空指针。以及它和STL容器结合会产生一堆未定义行为,例如一个 std::vector 存 std::auto_ptr ,如果使用 reserve 或者扩容触发内存重新分配,会导致可能一部分 std::auto_ptr 为空。
后续C++11引入了右值引用和移动语义的概念,这十分符合资源独占的需求,因此 std::auto_ptr 被废弃了,取而代之的是 std::unique_ptr。
然后是 SharedPtr,它是共享式的智能指针,允许多个指针共享同一个资源,通过引用计数来管理资源的生命周期。
前面的文章已经给了一个实现,但是我们这里由于要同时实现 WeakPtr ,而如果将进行引用计数特化为另一个类 ControlBlock 的话,那么 SharedPtr 和 WeakPtr 就能够共享这个 ControlBlock,实现会更加自然:
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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
| template<typename T> class ControlBlock { public: explicit ControlBlock(T* p) : ptr(p), use_count(1), weak_count(0) {}
T* get() const { return ptr; } size_t get_use_count() const { return use_count; } void add_ref() { ++use_count; } void release_ref() { if(--use_count == 0) { delete ptr; ptr = nullptr; if(weak_count == 0) delete this; } } void add_weak() { ++weak_count; } void release_weak() { if(--weak_count == 0 && use_count == 0) delete this; } private: T* ptr; size_t use_count; size_t weak_count; };
template<typename T> class WeakPtr;
template<typename T> class SharedPtr { friend class WeakPtr<T>; public: explicit SharedPtr(T* p = nullptr) : control(p ? new ControlBlock<T>(p) : nullptr) {} SharedPtr(const SharedPtr& other) : control(other.control) { if(control) control->add_ref(); } SharedPtr& operator=(const SharedPtr& other) { if(this != &other) { if(control) control->release_ref(); control = other.control; if(control) control->add_ref(); } return *this; } SharedPtr(SharedPtr&& other) noexcept : control(other.control) { other.control = nullptr; } SharedPtr& operator=(SharedPtr&& other) noexcept { if(this != &other) { if(control) control->release_ref(); control = other.control; other.control = nullptr; } return *this; } ~SharedPtr() { if(control) control->release_ref(); } T* get() const { return control ? control->get() : nullptr; } T& operator*() const { return *get(); } T* operator->() const { return get(); } size_t use_count() const { return control ? control->get_use_count() : 0; } explicit operator bool() const { return get() != nullptr; } void reset(T* p = nullptr) { if(control) control->release_ref(); control = p ? new ControlBlock<T>(p) : nullptr; }
private: ControlBlock<T>* control; };
template<typename T> class WeakPtr { public: WeakPtr() : control(nullptr) {} template<typename U> WeakPtr(const SharedPtr<U>& shared) : control(shared.control) { if(control) control->add_weak(); } WeakPtr(const WeakPtr& other) : control(other.control) { if(control) control->add_weak(); } WeakPtr& operator=(const WeakPtr& other) { if(this != &other) { if(control) control->release_weak(); control = other.control; if(control) control->add_weak(); } return *this; } WeakPtr(WeakPtr&& other) noexcept : control(other.control) { other.control = nullptr; } WeakPtr& operator=(WeakPtr&& other) noexcept { if(this != &other) { if(control) control->release_weak(); control = other.control; other.control = nullptr; } return *this; } ~WeakPtr() { if(control) control->release_weak(); } SharedPtr<T> lock() const { if(control && control->get_use_count() > 0) { SharedPtr<T> sp; sp.control = control; control->add_ref(); return sp; } return SharedPtr<T>(); } bool expired() const { return control == nullptr || control->get_use_count() == 0; } size_t use_count() const { return control ? control->get_use_count() : 0; }
private: ControlBlock<T>* control; };
|
面试的时候如果被问到智能指针的实现细节,我会将 UniquePtr 单独拎出来讲,它的Big Five中的拷贝构造和拷贝赋值直接删除掉,移动构造和移动赋值实现资源转移,析构函数负责释放资源。可以提一下 std::auto_ptr 的问题,以及为什么被废弃了。然后讲 SharedPtr 和 WeakPtr 的实现细节,重点讲解 ControlBlock 的设计,俩智能指针的Big Five怎么写,以及如何通过引用计数来管理资源的生命周期。
也可以稍微提一提RAII相关的东西。