RAII
了解智能指针前,我们先了解下C++编程中常用的RAII的思想
RAII全称为Resource Acquisition is Initialization,直译就是资源获取即初始化。
它的思想是将必须在使用前获取的资源(堆内存,线程,文件,互斥锁等)与一个对象的声明周期绑定,常用做法是在构造函数中请求资源,在析构函数中释放资源
以上由C++的语言机制保证,当对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数
智能指针就是使用RAII思想的例子
智能指针概述
智能指针本质是模板类独享,其行为类似指针。其最主要的功能是帮助管理动态内存,防止发生内存泄漏
C++提供了四种智能指针
- auto_ptr(C++11废弃,C++17移除)
- unique_ptr(C++11):独占智能指针
- shared_ptr(C++11):引用计数智能指针
- weak_ptr(C++11):弱引用智能指针,解决shared_ptr的循环引用,不能访问直接访问对象
1 2 3 4
| { auto_ptr<double> p(new double(10.0)); }
|
auto_ptr是C++98提供的,现已废弃,应使用unique_ptr或shared_ptr替代
考虑如下情况,将p赋值给p2会发生什么?
按照常规指针的做法,p2和p会指向同一个对象,但是如果这样,p2和p在析构时会释放两次内存,这当然是不可行的
1 2 3 4 5
| { auto_ptr<double> p(new double(10.0)); auto_ptr<double> p2; p2 = p; }
|
针对以上问题,有如下解决方案
- 执行深拷贝,但这会造成两个指针指向不同对象
- 建立所有权(ownership)的概念,仅可以让一个智能指针拥有该对象,该智能指针析构时执行内存释放操作。赋值操作将所有权转移。
- auto_ptr和unique_ptr都采用此策略,unique_ptr的策略更加严格
- 使用引用计数,记录有多少个智能指针指向该对象,当引用计数变为0时,执行内存释放操作
auto_ptr使用
- 不要将同一个原生指针赋值给多个智能指针
- auto_ptr对象的复制构造和赋值会转移所有权
- 作为参数不要按值传递
- auto_ptr不支持数组
- 无法作为STL容器的元素,auto_ptr的复制构造和赋值要求参数左值引用
- 仅支持new分配的内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| auto_ptr<double> p(new double(10.0)); auto_ptr<double> p2; p2 = p; *p = 10.0;
void test(auto_ptr<double> p3) { printf("%.2f\n", *p3); } test(p2); *p2 = 10.0;
auto_ptr<double[]> pa(new double[100]()); // bad
|
auto_ptr实现
- auto_ptr的复制构造函数和赋值运算符要求左值引用
- 需要考虑右值的构造和赋值,通过AutoPtrRef的方式实现
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
| template <typename T> struct AutoPtrRef { explicit AutoPtrRef(T* p) : ptr(p) {} T* ptr; };
template <typename T> class AutoPtr { public: using element_type = T;
explicit AutoPtr(element_type* data = nullptr) : data_(data) {} AutoPtr(AutoPtr& other) : data_(other.release()) {} AutoPtr(AutoPtrRef<element_type> ref) : data_(ref.ptr) {} template <typename Y> AutoPtr(AutoPtr<Y>& other) : data_(other.release()) {} ~AutoPtr() { Destroy(); }
AutoPtr& operator=(AutoPtr& other) { printf("calling AutoPtr::operator=(AutoPtr& other)\n"); reset(other.release()); return *this; } template <typename Y> AutoPtr& operator=(AutoPtr<Y>& other) { printf("calling AutoPtr::operator=(AutoPtr<Y>& other)\n"); reset(other.release()); return *this; } template <typename Y> AutoPtr& operator=(AutoPtrRef<Y> other) { printf("calling AutoPtr::operator=(AutoPtrRef<Y> other)\n"); reset(other.ptr); return *this; }
element_type* get() const { return data_; } element_type* operator->() const { return get(); } element_type& operator*() const { return *get(); }
template <typename Y> operator AutoPtrRef<Y>() { return AutoPtrRef<Y>(release()); } template <typename Y> operator AutoPtr<Y>() { return AutoPtr<Y>(release()); }
void reset(element_type* data = nullptr) { if (data != data_) { Destroy(); data_ = data; } } element_type* release() { element_type* ret = data_; data_ = nullptr; return ret; }
private: void Destroy() { if (data_) { delete data_; data_ = nullptr; } } element_type* data_; };
|
unique_ptr使用
相比于auto_ptr,unique_ptr有如下优点
- 不要将同一个原生指针赋值给多个智能指针
- 禁用拷贝构造函数和赋值运算符
- 使用移动语义std::move转移所有权
- 使用移动语义可以作为STL容器的元素,但某些需要拷贝和赋值的算法操作会受限
- 支持数组,并提供operator[],使用delete[]释放内存
- 支持自定义deleter
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
| { std::string name = "student1"; double score = 100.0; gtl::unique_ptr<Student> p(gtl::make_unique<Student>(name, score)); auto* p_bak = p.get(); EXPECT_NE(p.get(), nullptr); EXPECT_EQ(name, p->name()); EXPECT_EQ(score, p->score()); gtl::unique_ptr<Person> p2(std::move(p)); EXPECT_EQ(p.get(), nullptr); EXPECT_EQ(p2.get(), p_bak); EXPECT_EQ(name, p2->name()); p2 = std::move(p2); EXPECT_EQ(p2.get(), p_bak); EXPECT_EQ(name, p2->name());
gtl::unique_ptr<Person> p3; p3 = std::move(p2); } { int n = 10; gtl::unique_ptr<Person[]> p = gtl::make_unique<Person[]>(n); Person* p_bak = p.get(); EXPECT_NE(p.get(), nullptr); EXPECT_EQ(bool(p), true); p = std::move(p); EXPECT_NE(p.get(), nullptr); EXPECT_EQ(bool(p), true);
for (int i = 0; i < n; ++i) { p[i].set_name(std::to_string(i)); EXPECT_EQ(std::to_string(i), p[i].name()); } }
|
unique_ptr实现
- unique_ptr存在针对对象和数组的两个版本
- 分别使用new/delete和new[]/delete[]
- 需要支持自定义deleter
- 禁用拷贝构造函数和赋值运算符
- 定义移动构造函数和移动复制运算符
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
| template <typename T, typename Deleter = std::default_delete<T>> class UniquePtr { public: using pointer = T*; using element_type = T; using deleter_type = Deleter;
explicit UniquePtr(pointer data = nullptr, deleter_type deleter = deleter_type()) : data_(data), deleter_(deleter) {} UniquePtr(const UniquePtr& other) = delete; UniquePtr(UniquePtr&& other) : data_(other.release()), deleter_(std::forward<deleter_type>(other.deleter_)) {} template <typename Y> UniquePtr(UniquePtr<Y>&& other) : data_(other.release()), deleter_(std::forward<deleter_type>(other.get_deleter())) {} ~UniquePtr() { Destroy(); }
UniquePtr& operator=(const UniquePtr& other) = delete; UniquePtr& operator=(UniquePtr&& other) { reset(other.release()); deleter_ = std::forward<deleter_type>(other.deleter_); return *this; } template <typename Y> UniquePtr& operator=(UniquePtr<Y>&& other) { reset(other.release()); deleter_ = std::forward<deleter_type>(other.deleter_); return *this; }
pointer operator->() const { return get(); } element_type& operator*() const { return *get(); }
explicit operator bool() const { return data_ != nullptr; } deleter_type& get_deleter() { return deleter_; } const deleter_type& get_deleter() const { return deleter_; }
pointer get() const { return data_; } void reset(pointer data = nullptr) { if (data != data_) { Destroy(); data_ = data; } } pointer release() { pointer ret = data_; data_ = nullptr; return ret; }
void swap(UniquePtr& other) { std::swap(data_, other.data_); std::swap(deleter_, other.deleter_); }
private: void Destroy() { if (data_) { deleter_(data_); data_ = nullptr; } } pointer data_; deleter_type deleter_; };
template <typename T, typename Deleter> class UniquePtr<T[], Deleter> { public: using pointer = T*; using element_type = T; using deleter_type = Deleter;
explicit UniquePtr(pointer data = nullptr) : data_(data) {} UniquePtr(const UniquePtr& other) = delete; UniquePtr(UniquePtr&& other) : data_(other.release()), deleter_(std::forward<deleter_type>(other.deleter_)) {} ~UniquePtr() { Destroy(); }
UniquePtr& operator=(const UniquePtr& other) = delete; UniquePtr& operator=(UniquePtr&& other) { reset(other.release()); deleter_ = std::forward<deleter_type>(other.deleter_); return *this; } template <typename Y> UniquePtr& operator=(UniquePtr<Y>&& other) { reset(other.release()); deleter_ = std::forward<deleter_type>(other.deleter_); return *this; }
element_type& operator[](std::size_t index) const { return data_[index]; }
explicit operator bool() const { return data_ != nullptr; } deleter_type& get_deleter() { return deleter_; } const deleter_type& get_deleter() const { return deleter_; }
pointer get() const { return data_; } void reset(pointer data = nullptr) { Destroy(); data_ = data; } pointer release() { pointer ret = data_; data_ = nullptr; return ret; }
void swap(UniquePtr& other) { std::swap(data_, other.data_); std::swap(deleter_, other.deleter_); }
private: void Destroy() { if (data_) { deleter_(data_); data_ = nullptr; } } pointer data_; deleter_type deleter_; };
|
shared_ptr和weak_ptr使用
- 不要将同一个原生指针赋值给多个智能指针
- shared_ptr是共享所有权的智能指针,使用上更像普通指针
- 支持数组(C++17)
- 注意循环引用——将任一shared_ptr替换为weak_ptr打破循环即可
- weak_ptr无法访问其所管理的资源,其是为了解决循环引用的问题,weak_ptr不会增加引用计数
- weak_ptr可以通过expired函数判断对应的资源是否已经释放
- 访问所管理的对象必须通过lock函数返回一个shared_ptr对象(注意判断返回值是否为空,可能其所对应的资源已经释放)
- 线程安全问题
- 多个线程在不同shared_ptr的实例上调用所有成员函数(包含复制构造和赋值)是安全的,即使他们共享同一个对象的所有权
- 多个线程只读方式访问同一个shared_ptr的实例是安全的
- 多个线程访问同一个shared_ptr的实例的非const成员函数是不安全的,会出现数据竞争
- 所拥有资源的线程安全由资源本身决定
循环引用的例子
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
| class Student : public Person { public:
private: double score_; SharedPtr<Teacher> teacher_; };
class Teacher : public Person { public:
private: int id_; SharedPtr<Student> student_; WeakPtr<Student> student_weak_; };
{ gtl::shared_ptr<Teacher> teacher(gtl::make_shared<Teacher>("teacher", 10)); gtl::shared_ptr<Student> student(gtl::make_shared<Student>("student", 100.0)); printf("%s count %zu\n", teacher->name().c_str(), teacher.use_count()); printf("%s count %zu\n", student->name().c_str(), student.use_count()); EXPECT_EQ(teacher.use_count(), 1); EXPECT_EQ(student.use_count(), 1); teacher->set_student(student); student->set_teacher(teacher); printf("%s count %zu\n", teacher->name().c_str(), teacher.use_count()); printf("%s count %zu\n", student->name().c_str(), student.use_count()); EXPECT_EQ(teacher.use_count(), 2); EXPECT_EQ(student.use_count(), 2); }
{ gtl::WeakPtr<Student> student_weak_bak; { gtl::shared_ptr<Teacher> teacher_weak(gtl::make_shared<Teacher>("teacher_weak", 10)); gtl::shared_ptr<Student> student_weak(gtl::make_shared<Student>("student_weak", 100.0)); printf("%s count %zu\n", teacher_weak->name().c_str(), teacher_weak.use_count()); printf("%s count %zu\n", student_weak->name().c_str(), student_weak.use_count()); EXPECT_EQ(teacher_weak.use_count(), 1); EXPECT_EQ(student_weak.use_count(), 1); teacher_weak->set_student_weak(student_weak); student_weak->set_teacher(teacher_weak); printf("%s count %zu\n", teacher_weak->name().c_str(), teacher_weak.use_count()); printf("%s count %zu\n", student_weak->name().c_str(), student_weak.use_count()); EXPECT_EQ(teacher_weak.use_count(), 2); EXPECT_EQ(student_weak.use_count(), 1); student_weak_bak = student_weak; EXPECT_EQ(student_weak_bak.use_count(), 1); EXPECT_EQ(student_weak_bak.expired(), false); EXPECT_EQ(bool(student_weak_bak.lock()), true); printf("student_weak ptr %p\n", student_weak.get()); } printf("weak test\n"); EXPECT_EQ(student_weak_bak.use_count(), 0); EXPECT_EQ(student_weak_bak.expired(), true); auto stucent_shared_ptr = student_weak_bak.lock(); printf("student_weak ptr %p\n", stucent_shared_ptr.get()); EXPECT_EQ(bool(stucent_shared_ptr), false); }
|
shared_ptr和weak_ptr实现
- shared_ptr包含指向对象的指针和引用计数的指针
- 包含对象指针是为了不同的对象共享所有权
- 包含引用计数的指针而不是对象,是为了共享所有权的shared_ptr得到的引用计数是相同的
- 引用计数包含use_count和weak_count
- 由于weak_ptr需要在所有的shared_ptr都释放时也能够判断引用计数的数量是否为0,因此shared_ptr释放时,如果weak_count>1,则仅可以释放所管理对象的资源,引用计数的资源不能释放
- 当weak_ptr析构时,需要判断是否需要释放引用计数的资源
- shared_ptr的use_count和weak_count如何变化
- 以空指针构造,两个指针都为空
- 以非空指针构造,use_count和weak_count都设置为1
- 复制构造,若非空则use_count加1
- 移动构造use_count不变,资源转移到新构造的shared_ptr,被移动的shared_ptr变为空
- 赋值操作,左侧操作数use_count减1,右侧操作数use_count加1,并且赋值给左操作数,左右侧操作数指向同一对象
- 移动赋值,左侧操作数use_count减1,右侧操作数use_count不变,并且赋值给左操作数,右侧操作数变为空
- shared_ptr析构,use_count减1
- use_count为0时,释放所管理对象的资源,weak_count减1(为0时释放引用计数的资源)
- weak_ptr的use_count和weak_count如何变化
- 默认构造,weak_ptr不管理任何对象
- weak_ptr不接受普通指针构造
- 复制构造,若非空则weak_count加1
- 以shared_ptr构造,若非空则weak_count加1
- 移动构造,weak_count不变,被移动weak_ptr变为空
- 赋值操作,左侧操作数weak_count减1,右侧操作数weak_count加1,并且赋值给左操作数,左右侧操作数指向同一对象
- 移动赋值,左侧操作数weak_count减1,右侧操作数weak_count不变,并且赋值给左操作数,右侧操作数变为空
- weak_ptr析构,weak_count减1
- weak_count为0时,释放引用计数的资源
- 调用lock函数构造shared_ptr,use_count加1
具体实现可参考smart_pointers.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| template <typename T> struct RefCount { std::atomic<std::size_t> use_count; std::atomic<std::size_t> weak_count; };
class SharedPtr { public:
private: RefCount* ref_count_; T* data_; }
|
参考
Last updated: