简单手写一个智能指针,不会过多关注具体功能上的实现,而是思想上的实现。
比如指针上的诸多行为++,--
这里不会多次提到,而使一些关于智能指针特性和思想的实现。
RAII与手写智能指针
RAII准则,封装指针
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
| class shape { public:
shape() { cout<<"new shape"<<endl; } };
class shape_wrapper { public: explicit shape_wrapper(shape* ptr = nullptr):ptr_(ptr){} ~shape_wrapper() { delete ptr_; cout<<"delete ptr_"<<endl; } shape* get() const {return ptr_;} private: shape* ptr_; };
int main() {
shape_wrapper(new shape());
return 0; }
|
我们继续扩展:
模板封装
首先让智能指针不止受限于shape,利用模板即可
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
| class shape { public:
shape() { cout<<"new shape"<<endl; } };
template<typename T> class smart_ptr { public: explicit smart_ptr(T* ptr = nullptr):ptr_(ptr){} ~smart_ptr() { delete ptr_; cout<<"delete ptr_"<<endl; } T* get() const {return ptr_;} private: T* ptr_; };
int main() {
smart_ptr<shape>(new shape());
return 0; }
|
运算符重载模拟指针行为
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
| class shape { public:
shape() { cout<<"new shape"<<endl; } };
template<typename T> class smart_ptr { public: explicit smart_ptr(T* ptr = nullptr):ptr_(ptr){} ~smart_ptr() { delete ptr_; cout<<"delete ptr_"<<endl; } T* get() const {return ptr_;} T& operator*() const {return *ptr_;} T* operator->() const {return ptr_;} operator bool() const {return ptr_;} private: T* ptr_; };
int main() {
smart_ptr<shape>ptr(new shape());
return 0; }
|
添加拷贝构造和赋值
这是个很难定义行为的问题:
我们是否应该禁用这种行为:
| smart_ptr<shape> ptr1{new shape()}; smart_ptr<shape>ptr2{ptr1};
|
这会导致ptr2 ptr1释放同一片区域的内存两次造成崩溃,因此我们暂时考虑禁用这种行为:delete掉拷贝构造和赋值构造即可
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
| class shape { public:
shape() { cout<<"new shape"<<endl; } };
template<typename T> class smart_ptr { public: explicit smart_ptr(T* ptr = nullptr):ptr_(ptr){} smart_ptr(const smart_ptr&) = delete; smart_ptr& operator=(const smart_ptr&) = delete; ~smart_ptr() { delete ptr_; cout<<"delete ptr_"<<endl; } T* get() const {return ptr_;} T& operator*() const {return *ptr_;} T* operator->() const {return ptr_;} operator bool() const {return ptr_;} private: T* ptr_; };
int main() {
smart_ptr<shape>ptr(new shape()); return 0; }
|
也许我们可以更好的解决这个问题,不再禁用拷贝构造和赋值构造,而是转交指针的的所有权。
| smart_ptr(smart_ptr& other) { ptr_ = other.release(); } smart_ptr& operator=(smart_ptr& rhs) { smart_ptr(rhs).swap(*this); return *this }
|
这有个疑问: 赋值函数中不都会加上 if (this != &rhs)
吗?
那种用法更啰嗦,而且异常安全性不够好,如果在赋值过程中发生异常的话,this 对象的内容可能已经被部分破坏了,对象不再处于一个完整的状态。
目前这种惯用法(参考资料)则保证了强异常安全性:赋值分为拷贝构造和交换两步,异常只可能在第一步发生;而第一步如果发生异常的话,this 对象完全不受任何影响。无论拷贝构造成功与否,结果只有赋值成功和赋值没有效果两种状态,而不会发生因为赋值破坏了当前对象这种场景。
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
| class shape { public:
shape() { cout<<"new shape"<<endl; } };
template<typename T> class smart_ptr { public: explicit smart_ptr(T* ptr = nullptr):ptr_(ptr){} smart_ptr(smart_ptr& other) { ptr_ = other.release(); } smart_ptr& operator=(smart_ptr& rhs) { smart_ptr(rhs).swap(*this); return *this }
~smart_ptr() { delete ptr_; cout<<"delete ptr_"<<endl; } T* get() const {return ptr_;} T* release() { T* ptr = ptr_; ptr_ = nullptr; return ptr; } void swap(smart_ptr& rhs) { using std::swap; swap(ptr_, rhs.ptr_); } T& operator*() const {return *ptr_;} T* operator->() const {return ptr_;} operator bool() const {return ptr_;} private: T* ptr_; };
int main() {
smart_ptr<shape>ptr(new shape());
return 0; }
|
到此为止,这就是C++98给出auto_ptr
的定义。 不过在C++17已经把auto_ptr
删除掉了, 因为这个有很大的问题就是:程序员常常会不小心把smart_ptr
传送给另一个,你就不在拥有这个对象了。这里的问题就是你赋值/拷贝时到底希不希望移交所有权,如果不希望移交,只希望多个同时拥有那就是shared_ptr
,如果希望移交那就是unique_ptr
,那怎么在unique_ptr
移交时给一个程序员提醒呢? 利用左右值,当右值时才可以移交权力。
添加移动语义: unque_ptr() 雏形
把拷贝构造函数中的参数类型smart_ptr&
改成了 smart_ptr&&
, 现在它成了移动构造函数
把赋值函数中的参数类型 smart_ptr&
改成了smart_ptr
,在构造参数时直接生成新的智能指针,从而不再需要在函数体中构造临时对象。现在赋值函数的行为是移动还是拷贝,完全依赖于构造参数时走的是移动构造还是拷贝构造。
| smart_ptr(smart_ptr&& other) { ptr_ = other.release(); } smart_ptr& operator=(smart_ptr rhs) { rhs.swap(*this); return *this }
|
根据 C++ 的规则,如果我提供了移动构造函数而没有手动提供拷贝构造函数,那后者自动被禁用, 因此我们可以得到一个基本的unique_ptr
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
| class shape { public:
shape() { cout<<"new shape"<<endl; } };
template<typename T> class smart_ptr { public: explicit smart_ptr(T* ptr = nullptr):ptr_(ptr){}
smart_ptr(smart_ptr&& other) { ptr_ = other.release(); } smart_ptr& operator=(smart_ptr rhs) { rhs.swap(*this); return *this; }
~smart_ptr() { delete ptr_; cout<<"delete ptr_"<<endl; } T* get() const {return ptr_;} T* release() { T* ptr = ptr_; ptr_ = nullptr; return ptr; } void swap(smart_ptr& rhs) { using std::swap; swap(ptr_, rhs.ptr_); } T& operator*() const {return *ptr_;} T* operator->() const {return ptr_;} operator bool() const {return ptr_;} private: T* ptr_; };
int main() {
smart_ptr<shape>ptr1(new shape());
smart_ptr<shape>ptr2{ptr1};
smart_ptr<shape>ptr3; ptr3 = ptr1;
ptr3 = std::move(ptr1); smart_ptr<shape> ptr4{std::move(ptr3)};
return 0; }
|
小细节:子类指针向基类指针的转换
一个circle*
是可以隐式转换成shape*
的,但上面的smart_ptr<circle>
却无法自动转换成 smart_ptr<shape>
。这个行为显然还是不够“自然”。
我们只需要改变一下移动构造函数即可:再加一个模板即可
| template <typename U> smart_ptr(smart_ptr<U>&& other) { ptr_ = other.release(); }
|
我们自然而然利用了指针的转换特性:现在 smart_ptr<circle>
可以移动给smart_ptr<shape>
,但不能移动给smart_ptr<triangle>
。不正确的转换会在代码编译时直接报错。
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
| class shape { public:
shape() { cout<<"new shape"<<endl; } };
class circle:public shape { public: circle() { cout<<"new circle"<<endl; } };
template<typename T> class smart_ptr { public: explicit smart_ptr(T* ptr = nullptr):ptr_(ptr){}
template<typename U> smart_ptr(smart_ptr<U>&&other) { ptr_ = other.release(); }
smart_ptr& operator=(smart_ptr rhs) { rhs.swap(*this); return *this; }
~smart_ptr() { delete ptr_; cout<<"delete ptr_"<<endl; } T* get() const {return ptr_;} T* release() { T* ptr = ptr_; ptr_ = nullptr; return ptr; } void swap(smart_ptr& rhs) { using std::swap; swap(ptr_, rhs.ptr_); } T& operator*() const {return *ptr_;} T* operator->() const {return ptr_;} operator bool() const {return ptr_;} private: T* ptr_; };
int main() {
smart_ptr<shape>ptr1(new shape());
smart_ptr<circle>circle_ptr(new circle()); ptr1 = std::move(circle_ptr);
return 0; }
|
到此一个有基本功能unique_ptr
就完成了
shared_ptr
引用计数实现
我们需要搞一个类表示共享计数模块:
| class shared_count { public: shared_count():count_(1) {} void add_count() ++count_; long long reduce_count() --count_; long long get_count() const { return count_; } private: long long count_; };
|
初始化shared_ptr
需要构造一个shared_count
析构函数在看到 ptr_
非空时(此时根据代码逻辑,shared_count
也必然非空),需要对引用数减一,并在引用数降到零时彻底删除对象和共享计数。
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
| template <typename T> class smart_ptr { public: explicit smart_ptr(T* ptr = nullptr) : ptr_(ptr) { if (ptr) { shared_count_ = new shared_count(); } } ~smart_ptr() { if (ptr_ && !shared_count_ ->reduce_count()) { delete ptr_; delete shared_count_; } }
private: T* ptr_; shared_count* shared_count_; };
|
同理swap函数也应该swap掉引用计数模块:
| void swap(smart_ptr& rhs) { using std::swap; swap(ptr_, rhs.ptr_); swap(shared_count_, rhs.shared_count_); }
|
赋值函数可以跟前面一样,保持不变(这是因为赋值函数就是利用拷贝构造和swap实现的),但拷贝构造和移动构造函数是需要更新一下:更新计数功能
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
| smart_ptr(const smart_ptr& other) { ptr_ = other.ptr_; if (ptr_) { other.shared_count_ ->add_count(); shared_count_ = other.shared_count_; } } template <typename U> smart_ptr(const smart_ptr<U>& other) { ptr_ = other.ptr_; if (ptr_) { other.shared_count_ ->add_count(); shared_count_ = other.shared_count_; } } template <typename U> smart_ptr(smart_ptr<U>&& other) { ptr_ = other.ptr_; if (ptr_) { shared_count_ = other.shared_count_; other.ptr_ = nullptr; } }
|
拷贝构造函数为什么有一个泛型版本 还有一个非泛型版本 但是函数体内容又一模一样 不是代码冗余的吗 是有什么特殊设计意图吗?
这是一个很特殊的、甚至有点恼人的情况。如果没有非泛型版本,编译器看到没有拷贝构造函数,会生成一个缺省的拷贝构造函数。这样,同样类型的smart_ptr
的拷贝构造会是错误的。“子类指针向基类指针的转换”这一节里我也提到了这点。这不是我讲智能指针想讲的内容,所以就淡化了。
shared_ptr
去除release()
,改用use_count()
release()
是unique_ptr
特有的产物,我们不需要在shared_ptr
中存在这种东西,我们更需要的是看下引用计数器有多少个: 添加use_count()
| long use_count() const { if (ptr_) { return shared_count_ ->get_count(); } else { return 0; } }
|
shared_ptr
最终实现
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
| #include <utility> // std::swap
class shared_count { public: shared_count() noexcept : count_(1) {} void add_count() noexcept { ++count_; } long reduce_count() noexcept { return --count_; } long get_count() const noexcept { return count_; }
private: long count_; };
template <typename T> class smart_ptr { public: template <typename U> friend class smart_ptr;
explicit smart_ptr(T* ptr = nullptr) : ptr_(ptr) { if (ptr) { shared_count_ = new shared_count(); } } ~smart_ptr() { if (ptr_ && !shared_count_ ->reduce_count()) { delete ptr_; delete shared_count_; } }
smart_ptr(const smart_ptr& other) { ptr_ = other.ptr_; if (ptr_) { other.shared_count_ ->add_count(); shared_count_ = other.shared_count_; } } template <typename U> smart_ptr(const smart_ptr<U>& other) noexcept { ptr_ = other.ptr_; if (ptr_) { other.shared_count_->add_count(); shared_count_ = other.shared_count_; } } template <typename U> smart_ptr(smart_ptr<U>&& other) noexcept { ptr_ = other.ptr_; if (ptr_) { shared_count_ = other.shared_count_; other.ptr_ = nullptr; } } template <typename U> smart_ptr(const smart_ptr<U>& other, T* ptr) noexcept { ptr_ = ptr; if (ptr_) { other.shared_count_ ->add_count(); shared_count_ = other.shared_count_; } } smart_ptr& operator=(smart_ptr rhs) noexcept { rhs.swap(*this); return *this; }
T* get() const noexcept { return ptr_; } long use_count() const noexcept { if (ptr_) { return shared_count_ ->get_count(); } else { return 0; } } void swap(smart_ptr& rhs) noexcept { using std::swap; swap(ptr_, rhs.ptr_); swap(shared_count_, rhs.shared_count_); }
T& operator*() const noexcept { return *ptr_; } T* operator->() const noexcept { return ptr_; } operator bool() const noexcept { return ptr_; }
private: T* ptr_; shared_count* shared_count_; };
template <typename T> void swap(smart_ptr<T>& lhs, smart_ptr<T>& rhs) noexcept { lhs.swap(rhs); }
template <typename T, typename U> smart_ptr<T> static_pointer_cast( const smart_ptr<U>& other) noexcept { T* ptr = static_cast<T*>(other.get()); return smart_ptr<T>(other, ptr); }
template <typename T, typename U> smart_ptr<T> reinterpret_pointer_cast( const smart_ptr<U>& other) noexcept { T* ptr = reinterpret_cast<T*>(other.get()); return smart_ptr<T>(other, ptr); }
template <typename T, typename U> smart_ptr<T> const_pointer_cast( const smart_ptr<U>& other) noexcept { T* ptr = const_cast<T*>(other.get()); return smart_ptr<T>(other, ptr); }
template <typename T, typename U> smart_ptr<T> dynamic_pointer_cast( const smart_ptr<U>& other) noexcept { T* ptr = dynamic_cast<T*>(other.get()); return smart_ptr<T>(other, ptr); }
|