智能指针——shared_ptr的原理及仿写
shared_ptr的原理及仿写
共享指针允许多个指针指向同一份数据,因为它使用了引用计数,每多一个指针指向这个数据,引用技术加一,每销毁一个指针,引用技术减一,如果引用计数为0,则delete这个数据。
但是共享指针也不能将同一个裸指针赋值给多个智能指针,因为这样会是两个独立的共享指针,它们会分别计数,也就是意味着到时候它们会重复释放。
第一个没有计数器设计的版本:
template<typename T>
class MyShared_ptr {
private:
//内部指针
T* m_ptr;
//计数器
long* m_count;
public:
//以裸指针构造共享指针,默认为nullptr
//禁止隐式构造
explicit MyShared_ptr(T* ptr = nullptr) : m_ptr(ptr) {
//计数器初始化为1
m_count = new long(1);
}
//以MyUnique_ptr右值构造共享指针
MyShared_ptr(MyUnique_ptr<T>&& src) {
//销毁独占指针,取出裸指针
m_ptr = src.release();
//计数器初始化为1
m_count = new long(1);
}
//拷贝构造函数
MyShared_ptr(const MyShared_ptr& src) {
m_ptr = src.m_ptr;
//拷贝计数器指针
m_count = src.m_count;
//计数器加一
(*m_count)++;
}
//拷贝赋值
MyShared_ptr& operator=(const MyShared_ptr& src) {
if (&src == this) return *this;
//如果自身不为空,先调用析构函数
if (m_ptr != nullptr) {
this->~MyShared_ptr();
}
m_ptr = src.m_ptr;
m_count = src.m_count;
(*m_count)++;
return *this;
}
//以MyUnique_ptr右值 拷贝赋值
MyShared_ptr& operator=(const MyUnique_ptr<T>&& src) {
if (m_ptr != nullptr) {
this->~MyShared_ptr();
}
//销毁独占指针,取出裸指针
m_ptr = src.release();
m_count = new long(1);
return *this;
}
//析构函数
~MyShared_ptr() {
//计数器减一
(*m_count)--;
//如果计数器为0,则释放指针空间和计数器空间
if (0 == *m_count) {
delete m_ptr;
delete m_count;
m_ptr = nullptr;
m_count = nullptr;
}
}
//获得内部指针地址
T* get() {
return m_ptr;
}
//重置指针
void reset(T* ptr = nullptr) {
//先调用析构函数
this->~MyShared_ptr();
m_ptr = ptr;
m_count = new long(1);
}
//返回当前引用计数
long use_count() {
return *m_count;
}
//转换函数 bool
operator bool() const {
return m_ptr != nullptr;
}
//当前共享指针是否唯一
bool unique() {
if (1 == *m_count) return true;
else return false;
}
//成员函数交换指针和计数器
void swap(MyShared_ptr<T>& src) {
T* temp_ptr;
long* temp_count;
temp_ptr = src.m_ptr;
temp_count = src.m_count;
src.m_ptr = m_ptr;
src.m_count = m_count;
m_ptr = temp_ptr;
m_count = temp_count;
}
//重载*
T& operator*() const {
return *m_ptr;
}
//重载->
T* operator->() const {
return (&**this);
}
};
//全局函数,有右值引用make_MyShared
template<class T>
MyShared_ptr<T> make_MyShared(MyShared_ptr<T>&& src) {
return MyShared_ptr<T>(forward<MyShared_ptr<T>>(src));
}
有计数器设计的版本
//计数器设计
template<typename T>
class MyCount {
private:
//分别存储引用计数和弱指针计数
long use_count;
long weak_count;
public:
//计数器的初始化
MyCount(long u,long w) : use_count(u), weak_count(w) {}
//弱指针计数++
void Increase_weak_count() {
++weak_count;
}
//弱指针计数--
void Decrease_weak_count() {
--weak_count;
}
//引用计数++
void Increase_use_count() {
++use_count;
}
//引用计数--
void Decrease_use_count() {
--use_count;
}
//获得引用计数
long get_use_count() {
return use_count;
}
//释放传入对象的内存
void DestroyPtr(T* ptr) {
delete ptr;
}
//若没有弱指针,则释放计数器
void DestroyThis() {
if(0 == weak_count)
delete this;
}
};
template<typename T>
class MyShared_ptr {
private:
//内部指针
T* m_ptr;
//计数器指针
MyCount<T>* m_count;
public:
//以裸指针构造共享指针
explicit MyShared_ptr(T* ptr = nullptr) : m_ptr(ptr) {
//初始化计数器为 引用1 弱指针0
m_count = new MyCount<T>(1,0);
}
//以独占指针构造共享指针
MyShared_ptr(MyUnique_ptr<T>&& src) {
//销毁独占指针并取出裸指针
m_ptr = src.release();
//初始化计数器为 引用1 弱指针0
m_count = new MyCount<T>(1, 0);
}
//拷贝构造
MyShared_ptr(const MyShared_ptr& src) {
m_ptr = src.m_ptr;
m_count = src.m_count;
//引用计数++
m_count->Increase_use_count();
}
//拷贝赋值
MyShared_ptr& operator=(const MyShared_ptr& src) {
if (&src == this) return *this;
if (m_ptr != nullptr) {
this->~MyShared_ptr();
}
m_ptr = src.m_ptr;
m_count = src.m_count;
//引用计数++
m_count->Increase_use_count();
return *this;
}
//拷贝独占指针赋值
MyShared_ptr& operator=(const MyUnique_ptr<T>&& src) {
if (m_ptr != nullptr) {
this->~MyShared_ptr();
}
m_ptr = src.release();
m_count = new MyCount<T>(1, 0);
return *this;
}
//析构函数
~MyShared_ptr() {
//引用计数--
m_count->Decrease_use_count();
//如果引用计数为零
if (0 == m_count->get_use_count()) {
//释放指针
m_count->DestroyPtr(m_ptr);
//销毁计数器(计数器内部判断有无弱指针)
m_count->DestroyThis();
m_ptr = nullptr;
m_count = nullptr;
}
}
//获得内部指针地址
T* get() {
return m_ptr;
}
//重置共享指针为新指针
void reset(T* ptr = nullptr) {
this->~MyShared_ptr();
m_ptr = ptr;
m_count = new MyCount<T>(1, 0);
}
//返回引用计数
long use_count() {
return m_count->get_use_count();
}
//转换函数
operator bool() const {
return m_ptr != nullptr;
}
//是否唯一
bool unique() {
if (1 == m_count->get_use_count()) return true;
else return false;
}
//交换指针和计数器
void swap(MyShared_ptr<T>& src) {
T* temp_ptr;
MyCount<T>* temp_count;
temp_ptr = src.m_ptr;
temp_count = src.m_count;
src.m_ptr = m_ptr;
src.m_count = m_count;
m_ptr = temp_ptr;
m_count = temp_count;
}
//重载*
T& operator*() const {
return *m_ptr;
}
//重载->
T* operator->() const {
return (&**this);
}
//设置弱指针类为友元类
template<typename T>
friend class MyWeak_ptr;
};
//全局函数,有右值引用make_MyShared
template<class T>
MyShared_ptr<T> make_MyShared(MyShared_ptr<T>&& src) {
return MyShared_ptr<T>(forward<MyShared_ptr<T>>(src));
}
222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
C++ 11 智能指针shared_ptr类成员函数
李小虎
关注自动驾驶/机器人,RPC,深度学习,Linux
关注他
2 人赞同了该文章
C++ 11 模板库的 <memory> 头文件中定义的智能指针,即 shared_ptr 模板类,用来管理指针的存储,提供有限的内存回收函数,可同时与其他对象共享该管理功能,从而帮助彻底消除内存泄漏和悬空指针的问题。
shared_ptr 类型的对象能够获得指针的所有权并共享该所有权:一旦他们获得所有权,指针的所有者组就会在最后一个释放该所有权时负责删除该指针。
shared_ptr 对象一旦它们自己被销毁,或者它们的值因赋值操作或显式调用 shared_ptr::reset 而改变时,就会释放它们共同拥有的对象的所有权。一旦通过指针共享所有权的所有 shared_ptr 对象都释放了该所有权,则删除托管对象(通常通过调用 ::delete,也可以在构造时指定不同的删除器)。
- 同一个shared_ptr被多个线程读,是线程安全的;
- 同一个shared_ptr被多个线程写,不是线程安全的;
- 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。
shared_ptr 对象只能通过复制它们的值来共享所有权:如果两个 shared_ptr 是从同一个(非shared_ptr)指针构造(或制造)的,它们都将拥有该指针而不共享它,当其中一个释放时会导致潜在的访问问题它(删除其托管对象)并将另一个指向无效位置。
此外,shared_ptr 对象可以共享一个指针的所有权,同时指向另一个对象。这种能力被称为别名(参见构造函数),通常用于在拥有成员对象时指向成员对象。因此,一个 shared_ptr 可能与两个指针相关:
1)一个存储的指针,即它所指向的指针,以及它用 operator* 取消引用的指针。
2)一个所有者的指针(可能是共享的),它是所有权组负责在某个时间点删除的指针,并计为使用。
通常,存储指针和所有者指针指向同一个对象,但别名 shared_ptr 对象(使用别名构造函数及其副本构造的对象)可能指向不同的对象。不拥有任何指针的 shared_ptr 称为null shared_ptr。不指向任何对象的 shared_ptr 称为null shared_ptr 并且不应取消引用。请注意,空的 shared_ptr 不一定是null shared_ptr,null shared_ptr 也不一定是空的 shared_ptr。shared_ptr 对象通过提供对它们通过运算符 * 和 -> 指向的对象的访问来复制有限的指针功能。出于安全原因,它们不支持指针算术。类似weak_ptr,能够与 shared_ptr 对象共享指针,而无需拥有它们。shared_ptr 有以下成员函数:
(1)构造函数
shared_ptr的构造函数根据使用的参数类型构造 shared_ptr 对象:
1) 默认构造函数:constexpr shared_ptr() noexcept;
2) 从空指针构造:constexpr shared_ptr(nullptr_t) : shared_ptr() {}
3) 从指针构造:template <class U> explicit shared_ptr (U* p);
auto sp_sub = std::shared_ptr<Sub<Message>>(sub, del(addr)); //sub为new创建的对象
4) 从指针 + 删除器构造:template <class U, class D> shared_ptr (U* p, D del); template <class D> shared_ptr (nullptr_t p, D del);
5) 从指针 + 删除器 + 分配器构造:template <class U, class D, class Alloc> shared_ptr (U* p, D del, Alloc alloc); template <class D, class Alloc> shared_ptr (nullptr_t p, D del, Alloc alloc);
6) 复制构造函数:shared_ptr (const shared_ptr& x) noexcept; template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;
7) 从weak_ptr 复制:template <class U> explicit shared_ptr (const weak_ptr<U>& x);
8) 移动构造函数:shared_ptr (shared_ptr&& x) noexcept; template <class U> shared_ptr (shared_ptr<U>&& x) noexcept;
9) 从其他类型的托管指针移动:template <class U> shared_ptr (auto_ptr<U>&& x); template <class U, class D> shared_ptr (unique_ptr<U,D>&& x);
10) 别名构造函数:template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;
默认构造函数 1) 和 2)对象为空(不拥有指针,使用计数为零)。从指针构造3)该对象拥有 p,将使用计数设置为 1。从指针 + 删除器构造 4)与 3) 相同,但该对象还拥有删除器 del 的所有权(并在某些时候需要删除 p 时使用它)。从指针 + 删除器 + 分配器构造 5)与 4) 相同,但内部使用所需的任何内存都是使用 alloc 分配的(对象保留一份副本,但不取得所有权)。复制构造函数 6)如果 x 不为空,则对象共享 x 资产的所有权并增加使用次数。如果 x 为空,则构造一个空对象(如同默认构造)。从weak_ptr 7) 复制同上6),除了如果 x 已经过期,则抛出 bad_weak_ptr 异常。移动构造函数 8)该对象获取由 x 管理的内容,包括其拥有的指针。 x 变成一个空对象(就像默认构造的一样)。从其他类型的托管指针移动 9)对象获取由 x 管理的内容并将使用计数设置为 1。放弃的对象变为空,自动失去指针的所有权。别名构造函数 10)同6),除了存储的指针是p。该对象不拥有 p,也不会管理其存储。相反,它共同拥有 x 的托管对象并算作 x 的一种额外使用。它还将在发布时删除 x 的指针(而不是 p)。它可以用来指向已经被管理的对象的成员。
1) p: 其所有权被对象接管的指针。此指针值不应已由任何其他托管指针管理(即,此值不应来自托管指针上的调用成员 get)。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。
2) del: 用于释放拥有的对象的删除器对象。这应该是一个可调用对象,将指向 T 的指针作为其函数调用的参数(其中 T 是 shared_ptr 的模板参数)。
3) alloc:用于分配/取消分配内部存储的分配器对象。
4) X: 托管指针类型的对象。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。
#include <iostream>
#include <memory>
struct C {int* data;};
int main () {
std::shared_ptr<int> p1;
std::shared_ptr<int> p2 (nullptr);
std::shared_ptr<int> p3 (new int);
std::shared_ptr<int> p4 (new int, std::default_delete<int>());
std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
std::shared_ptr<int> p6 (p5); std::shared_ptr<int> p7 (std::move(p6));
std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
std::shared_ptr<C> obj (new C);
std::shared_ptr<int> p9 (obj, obj->data);
std::cout << "use_count:\n";
std::cout << "p1: " << p1.use_count() << '\n'; std::cout << "p2: " << p2.use_count() << '\n'; std::cout << "p3: " << p3.use_count() << '\n'; std::cout << "p4: " << p4.use_count() << '\n'; std::cout << "p5: " << p5.use_count() << '\n'; std::cout << "p6: " << p6.use_count() << '\n'; std::cout << "p7: " << p7.use_count() << '\n'; std::cout << "p8: " << p8.use_count() << '\n'; std::cout << "p9: " << p9.use_count() << '\n'; return 0; }
(2)析构函数
析构函数的作用是销毁shared_ptr对象。 但是,在此之前,根据成员 use_count 的值,它可能会产生以下副作用:
1)如果 use_count 大于 1(即该对象与其他 shared_ptr 对象共享其托管对象的所有权):与其共享所有权的其他对象的使用计数减 1。
2)如果 use_count 为 1(即对象是托管指针的唯一所有者):删除其拥有指针所指向的对象(如果 shared_ptr 对象是用特定的删除器构造的,则调用此函数;否则,函数使用运算符 删除)。
3)如果 use_count 为零(即对象为空),则该析构函数没有副作用。
用法举例:
#include <iostream> #include <memory> int main() { auto deleter = [](int* p) { std::cout << "[deleter called]\n"; delete p; }; std::shared_ptr<int> foo(new int, deleter); std::cout << "use_count: " << foo.use_count() << '\n'; return 0; // [deleter called] }
(3)赋值运算“=”
1) 复制:shared_ptr& operator= (const shared_ptr& x) noexcept; template <class U> shared_ptr& operator= (const shared_ptr<U>& x) noexcept;
2) 移动:shared_ptr& operator= (shared_ptr&& x) noexcept; template <class U> shared_ptr& operator= (shared_ptr<U>&& x) noexcept;
3) 从...移动:template <class U> shared_ptr& operator= (auto_ptr<U>&& x); template <class U, class D> shared_ptr& operator= (unique_ptr<U,D>&& x);
复制分配1) 将对象添加为 x 资产的共享所有者,从而增加它们的 use_count。移动分配 2) 将所有权从 x 转移到 shared_ptr 对象而不改变 use_count。 x 变成一个空的 shared_ptr(就像默认构造的一样)。同样,来自其他托管指针类型 3) 的移动分配也会转移所有权,并使用 set a use count of 1 进行初始化。
此外,在上述所有情况下,对该函数的调用与在其值更改之前调用了 shared_ptr 的析构函数具有相同的副作用(如果此 shared_ptr 是唯一的,则包括删除托管对象)。不能将指针的值直接分配给 shared_ptr 对象。您可以改用 make_shared 或成员重置。
托管指针类型的对象。U* 应隐式转换为 T*(其中 T 是 shared_ptr 的模板参数)。
用法举例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo; std::shared_ptr<int> bar (new int(10)); foo = bar; // copy bar = std::make_shared<int> (20); // move std::unique_ptr<int> unique (new int(30)); foo = std::move(unique); // move from unique_ptr std::cout << "*foo: " << *foo << '\n'; std::cout << "*bar: " << *bar << '\n'; return 0; }
(4)swap函数
函数声明:void swap (shared_ptr& x) noexcept; 参数x: 另一个相同类型的 shared_ptr 对象(即,具有相同的类模板参数 T)。作用是将 shared_ptr 对象的内容与 x 的内容交换,在它们之间转移任何托管对象的所有权,而不会破坏或改变两者的使用计数。
用法举例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo (new int(10)); std::shared_ptr<int> bar (new int(20)); foo.swap(bar); std::cout << "*foo: " << *foo << '\n'; std::cout << "*bar: " << *bar << '\n'; return 0; }
(5)reset函数
重置shared_ptr,对于声明1) 对象变为空(如同默认构造)。在所有其他情况下,shared_ptr 以使用计数为 1 获取 p 的所有权,并且 - 可选地 - 使用 del 和/或 alloc 作为删除器 和分配器。另外,调用这个函数有同样的副作用,就像在它的值改变之前调用了shared_ptr 的析构函数一样(包括删除托管对象,如果这个shared_ptr 是唯一的)。
1) void reset() noexcept;
2) template <class U> void reset (U* p);
3) template <class U, class D> void reset (U* p, D del);
4) template <class U, class D, class Alloc> void reset (U* p, D del, Alloc alloc);
用法举例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> sp; // empty sp.reset (new int); // takes ownership of pointer *sp=10; std::cout << *sp << '\n'; sp.reset (new int); // deletes managed object, acquires new pointer *sp=20; std::cout << *sp << '\n'; sp.reset(); // deletes managed object return 0; }
(6)get函数
函数声明:element_type* get() const noexcept; get()返回存储的指针。存储的指针指向shared_ptr对象解引用的对象,一般与其拥有的指针相同。存储的指针(即这个函数返回的指针)可能不是拥有的指针(即对象销毁时删除的指针)如果 shared_ptr 对象是别名(即,别名构造的对象及其副本)。
用法举例:
#include <iostream> #include <memory> int main () { int* p = new int (10); std::shared_ptr<int> a (p); if (a.get()==p) std::cout << "a and p point to the same location\n"; // three ways of accessing the same address: std::cout << *a.get() << "\n"; std::cout << *a << "\n"; std::cout << *p << "\n"; return 0; }
(7)取对象运算“*”
函数声明:element_type& operator*() const noexcept; 取消引用对象。返回对存储指针指向的对象的引用。等价于:*get()。如果shared_ptr的模板参数为void,则该成员函数是否定义取决于平台和编译器,以及它的返回类型 在这种情况下。
用法举例:
#include <iostream> #include <memory> int main() { std::shared_ptr<int> foo(new int); std::shared_ptr<int> bar(new int(100)); *foo = *bar * 2; std::cout << "foo: " << *foo << '\n'; std::cout << "bar: " << *bar << '\n'; return 0; }
(8)“->”操作符
函数声明:element_type* operator->() const noexcept; 取消引用对象成员。返回一个指向存储指针指向的对象的指针,以便访问其成员之一。如果存储的指针是空指针,则不应调用该成员函数,它返回与 get() 相同的值。
用法举例:
#include <iostream> #include <memory> struct C { int a; int b; }; int main() { std::shared_ptr<C> foo; std::shared_ptr<C> bar(new C); foo = bar; foo->a = 10; bar->b = 20; if (foo) std::cout << "foo: " << foo->a << ' ' << foo->b << '\n'; if (bar) std::cout << "bar: " << bar->a << ' ' << bar->b << '\n'; return 0; }
(9)use_count函数
函数声明:long int use_count() const noexcept; use_count 返回与此对象(包括它)在同一指针上共享所有权的 shared_ptr 对象的数量。如果这是一个空的 shared_ptr,则该函数返回零。库实现不需要保留任何特定所有者集的计数 ,因此调用此函数可能效率不高。 要具体检查 use_count 是否为 1,也可以使用 member unique 代替,这样可能更快。
(10)unique函数
函数声明:bool unique() const noexcept; 检查是否唯一 返回 shared_ptr 对象是否不与其他 shared_ptr 对象共享其指针的所有权(即,它是唯一的)。 空指针从来都不是唯一的(因为它们不拥有任何指针)。 如果唯一的 shared_ptr 对象释放此所有权,则它们负责删除其托管对象(请参阅析构函数)。 此函数应返回与 (use_count()==1) 相同的值,尽管它可能以更有效的方式执行此操作。 如果这是唯一的 shared_ptr,则返回值 true,否则返回 false。
用法举例:
#include <iostream> #include <memory> int main() { std::shared_ptr<int> foo; std::shared_ptr<int> bar(new int); std::cout << "foo unique?\n" << std::boolalpha; std::cout << "1: " << foo.unique() << '\n'; // false (empty) foo = bar; std::cout << "2: " << foo.unique() << '\n'; // false (shared with bar) bar = nullptr; std::cout << "3: " << foo.unique() << '\n'; // true return 0; }
(11)“bool”操作
函数声明:explicit operator bool() const noexcept; 检查是否为 null。 返回存储的指针是否为空指针。 存储的指针指向 shared_ptr 对象解除引用的对象,通常与其拥有的指针相同(销毁时删除的指针)。 如果 shared_ptr 对象是别名(即别名构造的对象及其副本),它们可能会有所不同。该函数返回的结果与 get()!=0 相同。 请注意,空的 shared_ptr(即此函数返回 false 的指针)不一定是空的 shared_ptr。 别名可能拥有某个指针但指向空,或者所有者组甚至可能拥有空指针(参见构造函数 4 和 5)。
用法举例:
#include <iostream> #include <memory>
int main() {
std::shared_ptr<int> foo;
std::shared_ptr<int> bar(new int(34));
if (foo)
std::cout << "foo points to " << *foo << '\n';
else
std::cout << "foo is null\n";
if (bar)
std::cout << "bar points to " << *bar << '\n';
else std::cout << "bar is null\n"; return 0; }
(12)owner_before函数
函数声明:
template <class U> bool owner_before (const shared_ptr<U>& x) const;
template <class U> bool owner_before (const weak_ptr<U>& x) const;
基于所有者的排序。根据严格的弱基于所有者的顺序返回是否认为对象在 x 之前。与 operator< 重载不同,此排序考虑了 shared_ptr 的拥有指针,而不是存储的指针,使得两个 如果它们都共享所有权,或者它们都为空,即使它们存储的指针值不同,这些对象中的一个被认为是等效的(即,无论操作数的顺序如何,该函数都返回 false)。 如果 shared_ptr 对象是一个别名(别名构造的对象及其副本),则 shared_ptr 对象解引用)可能不是拥有的指针(即对象销毁时删除的指针)。该函数由 owner_less 调用以确定其结果。
用法举例:
#include <iostream> #include <memory> int main() { int* p = new int(10); std::shared_ptr<int> a(new int(20)); std::shared_ptr<int> b(a, p); // alias constructor std::cout << "comparing a and b...\n" << std::boolalpha; std::cout << "value-based: " << (!(a < b) && !(b < a)) << '\n'; std::cout << "owner-based: " << (!a.owner_before(b) && !b.owner_before(a)) << '\n'; delete p; return 0; }