RAII RAII即Resource Acquisition is Initialization(资源获取即初始化)
在智能指针中添加了引用计数的概念,引用计数这种计数是为了防止内存泄露而产生的。 基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次, 每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
std::unique_ptr std::unique_ptr
是一种智能指针,它通过指针持有并管理另一对象(对其负责),并在 unique_ptr
离开作用域时释放该对象。
在发生下列两者之一时,用关联的删除器释放对象:(总结就是这个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 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 #include <cassert> #include <cstdio> #include <fstream> #include <iostream> #include <memory> #include <stdexcept> struct B { virtual ~B () = default ; virtual void bar () { std::cout << "B::bar\n" ; } }; struct D : B{ D () { std::cout << "D::D\n" ; } ~D () override { std::cout << "D::~D\n" ; } void bar () override { std::cout << "D::bar\n" ; } }; std::unique_ptr<D> pass_through (std::unique_ptr<D> p) { p->bar (); return p; } void close_file (std::FILE* fp) { std::fclose (fp); } struct List { struct Node { int data; std::unique_ptr<Node> next; }; std::unique_ptr<Node> head; ~List () { while (head) { auto next = std::move (head->next); head = std::move (next); } } void push (int data) { head = std::make_unique <Node>(data, std::move (head->next)); } }; int main () { std::cout << "1) 独占所有权语义演示\n" ; { std::unique_ptr<D> p = std::make_unique <D>(); std::unique_ptr<D> q = pass_through (std::move (p)); assert (!p); } std::cout << "\n" "2) 运行时多态演示\n" ; { std::unique_ptr<B> p = std::make_unique <D>(); p->bar (); } std::cout << "\n" "3) 自定义删除器演示\n" ; std::ofstream ("demo.txt" ) << 'x' ; { using unique_file_t = std::unique_ptr<std::FILE, decltype (&close_file)>; unique_file_t fp (std::fopen("demo.txt" , "r" ), &close_file) ; if (fp) std::cout << static_cast <char >(std::fgetc (fp.get ())) << '\n' ; } std::cout << "\n" "4) 自定义 lambda 表达式删除器和异常安全性演示\n" ; try { std::unique_ptr<D, void (*) (D*) > p (new D, [](D* ptr) { std::cout << "由自定义删除器销毁...\n" ; delete ptr; }) ; throw std::runtime_error ("" ); } catch (const std::exception&) { std::cout << "捕获到异常\n" ; } std::cout << "\n" "5) 数组形式的 unique_ptr 演示\n" ; { std::unique_ptr<D[]> p (new D[3 ]) ; } }
std::shared_ptr std::shared_ptr
是一种智能指针,它能够记录多少个 shared_ptr
共同指向一个对象,从而消除显式的调用 delete
,当引用计数变为零的时候就会将对象自动删除。
使用make_shared<T>(T&&... args)
,通过传入一个对象(make_shared内使用了完美转发)来获取这个对象类型的shared_ptr
指针。
std::shared_ptr
可以通过 get()
方法来获取原始指针,通过 reset()
来减少一个引用计数, 并通过use_count()
来查看一个对象的引用计数。
std::shared_ptr
是一种通过指针保持对象共享所有权的智能指针。多个 shared_ptr
对象可持有同一对象。下列情况之一出现时销毁对象并解分配其内存:
最后剩下的持有对象的 shared_ptr
被销毁;
最后剩下的持有对象的 shared_ptr
被通过 operator= 或 reset() 赋值为另一指针。
但是在下面代码,存在着内存泄漏的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <memory> #include <iostream> struct A ;struct B ;struct A { std::shared_ptr<B> pointer; ~A () { std::cout << "A 被销毁" << std::endl; } }; struct B { std::shared_ptr<A> pointer; ~B () { std::cout << "B 被销毁" << std::endl; } }; int main () { auto a = std::make_shared <A>(); auto b = std::make_shared <B>(); a->pointer = b; b->pointer = a; }
std::weak_ptr std::weak_ptr
是一种智能指针,它持有被 std::shared_ptr 管理的对象的非拥有性“弱”引用。在访问引用的对象前必须先转换为 std::shared_ptr 。(使用lock())
weak_ptr不持有对象的生命周期,不算入引用计数
但是可以用weak_ptr判断对象是否过期
std::weak_ptr
实现临时所有权:当某个对象只有存在时才需要被访问,且随时可能被他人删除时,可以使用 std::weak_ptr
来跟踪该对象,需要获得临时所有权时,将其转换为 std::shared_ptr 。如果此时销毁了原始 std::shared_ptr ,则对象的生命周期将被延长,直到临时 std::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 #include <memory> #include <iostream> struct A ;struct B ;struct A { std::shared_ptr<B> pointer; void print () { std::cout << "A" <<std::endl; } ~A () { std::cout << "A 被销毁" << std::endl; } }; struct B { std::weak_ptr<A> pointer; void print () { std::cout << "B" <<std::endl; } ~B () { std::cout << "B 被销毁" << std::endl; } }; int main () { auto a = std::make_shared <A>(); auto b = std::make_shared <B>(); a->pointer->print (); b->pointer.lock ()->print (); }
引用计数的实现 在cpp中,使用shared_ptr
的时候,编译器会创建一个Control Block,控制块的实现思想主要是通过封装 引用计数 和 删除器 来确保对象的生命周期由多个智能指针共享时,能够安全、有效地管理资源的分配和释放。
控制块的概念
控制块是一个管理 shared_ptr
引用计数和其他元数据的结构。它包含了:
强引用计数(use_count) :追踪有多少个 shared_ptr
实例引用了这个对象。
弱引用计数(weak_count) :追踪有多少个 weak_ptr
引用了该对象,确保只有所有强引用计数为零时,控制块才会被销毁。
删除器(deleter) :负责销毁被管理的对象。当引用计数归零时,删除器会被调用来销毁对象。
分配器(allocator) :如果 shared_ptr
使用了自定义分配器,控制块也会包含该分配器的状态。
控制块的结构设计
控制块通常是一个独立的结构体或类,它和被管理对象的内存分开存储。这样设计的好处是能确保内存和对象的生命周期是独立管理的,避免了直接将对象和引用计数存在同一块内存中可能导致的效率问题。
下面是一个简单的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 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 #ifndef SHARED_PTR_H #define SHARED_PTR_H #pragma once #include <iostream> #include <functional> #include <memory> #include <stdexcept> template <typename T>class shared_ptr ;struct ControlBlock { size_t ref_count; void * ptr; std::function<void (void *)> deleter; ControlBlock () : ref_count (0 ), ptr (nullptr ), deleter ([](void *){}) {} ControlBlock (size_t ref_count, void * ptr) : ref_count (ref_count), ptr (ptr), deleter ([](void * p) {}) {} ControlBlock (size_t ref_count, void * ptr, std::function<void (void *)> d) : ref_count (ref_count), ptr (ptr), deleter (std::move (d)) {} ~ControlBlock () = default ; }; template <typename T>class shared_ptr { ControlBlock* control_block; T* ptr; void release () ; public : shared_ptr () noexcept : control_block (nullptr ), ptr (nullptr ) {} explicit shared_ptr (T* p) ; shared_ptr (const shared_ptr& other) noexcept ; shared_ptr (shared_ptr&& other) noexcept ; template <typename ... Args> static shared_ptr<T> make_shared (Args&&... args) ; ~shared_ptr (); shared_ptr& operator =(const shared_ptr& other) noexcept ; shared_ptr& operator =(shared_ptr&& other) noexcept ; T& operator *() const ; T* operator ->() const ; T* get () const noexcept ; void reset (T* p = nullptr ) ; [[nodiscard]] size_t use_count () const noexcept ; explicit operator bool () const noexcept ; void swap (shared_ptr& other) noexcept ; bool operator ==(const shared_ptr& other) const noexcept ; bool operator !=(const shared_ptr& other) const noexcept ; }; template <typename T>shared_ptr<T>::shared_ptr (T* p) : control_block (new ControlBlock (1 , p)), ptr (p) {} template <typename T>shared_ptr<T>::shared_ptr (const shared_ptr& other) noexcept : control_block (other.control_block), ptr (other.ptr) { if (control_block) { control_block->ref_count++; } } template <typename T>shared_ptr<T>::shared_ptr (shared_ptr&& other) noexcept : control_block (other.control_block), ptr (other.ptr) { other.control_block = nullptr ; other.ptr = nullptr ; } template <typename T>template <typename ... Args>shared_ptr<T> shared_ptr<T>::make_shared (Args&&... args) { return shared_ptr <T>(new T (std::forward<Args>(args)...)); } template <typename T>shared_ptr<T>::~shared_ptr () { release (); } template <typename T>void shared_ptr<T>::release () { if (control_block) { control_block->ref_count--; if (control_block->ref_count == 0 ) { if (ptr) { control_block->deleter (ptr); } delete control_block; } } } template <typename T>shared_ptr<T>& shared_ptr<T>::operator =(const shared_ptr& other) noexcept { if (this != &other) { release (); control_block = other.control_block; ptr = other.ptr; if (control_block) { control_block->ref_count++; } } return *this ; } template <typename T>shared_ptr<T>& shared_ptr<T>::operator =(shared_ptr&& other) noexcept { if (this != &other) { release (); control_block = other.control_block; ptr = other.ptr; other.control_block = nullptr ; other.ptr = nullptr ; } return *this ; } template <typename T>T& shared_ptr<T>::operator *() const { if (ptr == nullptr ) { throw std::runtime_error ("Attempting to dereference a null pointer" ); } return *ptr; } template <typename T>T* shared_ptr<T>::operator ->() const { return ptr; } template <typename T>T* shared_ptr<T>::get () const noexcept { return ptr; } template <typename T>void shared_ptr<T>::reset (T* p) { release (); ptr = p; control_block = new ControlBlock (1 , p, [](void * p) { delete static_cast <T*>(p); }); } template <typename T>size_t shared_ptr<T>::use_count () const noexcept { if (control_block) { return control_block->ref_count; } return 0 ; } template <typename T>bool shared_ptr<T>::operator ==(const shared_ptr& other) const noexcept { return ptr == other.ptr; } template <typename T>bool shared_ptr<T>::operator !=(const shared_ptr& other) const noexcept { return ptr != other.ptr; } template <typename T>shared_ptr<T>::operator bool () const noexcept { return ptr != nullptr ; } template <typename T>void shared_ptr<T>::swap (shared_ptr& other) noexcept { std::swap (control_block, other.control_block); std::swap (ptr, other.ptr); } #endif