关注公众号获取更多信息:
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
1. shared_ptr是什么
首先shared_ptr是一个模板类。它是一种重载了->和*操作符的类,由类来实现对内存的管理,确保即使有异常产生,也可以通过智能指针类的析构函数完成内存的释放。
2. 为什么要用shared_ptr
shared_ptr并不能使程序变得更快,相反,由于控制块的引入,会使程序占用更多的内存。相应的引用计数逻辑会增加程序的运行开销。那么为什么要使用shred_ptr呢?
share_ptr,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。
每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候, 内存才会被释放。确保不会多次释放或者悬空指针。
归根到底一句话就是:尽量减少内存的错误使用。
如果你写程序靠谱,不会出现内存泄漏,重复删除指针,悬空指针等情况,建议不要使用shared_ptr。
3. shared_ptr是如何实现它的特性的
可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数析构函数(destructor)来完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
4. shared_ptr 如何创建
可以通过构造函数、赋值函数或者make_shared函数初始化智能指针。
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。当要用make_shared时,必须指定想要创建的对象的类型,定义方式与模板类相同。在函数名之后跟一个尖括号,在其中给出类型。例如,调用make_shared<string>时传递的参数必须与string的某个构造函数相匹配。如果不传递任何参数,对象就会进行值初始化。
Note:1. shared_ptr的默认能力是管理动态内存,但支持自定义的Deleter以实现个性化的资源释放动作。
2. 如果我们不初始化一个智能指针,它就会被初始化成一个空指针,接受指针参数的职能指针是explicit的,因此我们不能将一个内置指针隐式转换为一个智能指针,必须直接初始化形式来初始化一个智能指针
shared_ptr<int> p1 = new int(1024);//错误:必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));//正确:使用了直接初始化形式
使用shared_ptr注意事项:
不要把一个原生指针给多个shared_ptr管理 |
不要把this指针给shared_ptr |
不要在函数实参里创建shared_ptr |
不要在函数实参里创建shared_ptr |
通常用auto定义一个对象来保存make_shared的结果 |
不要不加思考地把指针替换为shared_ptr来防止内存泄漏,shared_ptr并不是万能的,而且使用它们的话也是需要一定的开销的 |
环状的链式结构shared_ptr将会导致内存泄漏(可以结合weak_ptr来解决) |
共享拥有权的对象一般比限定作用域的对象生存更久,从而将导致更高的平均资源使用时间 |
在多线程环境中使用共享指针的代价非常大,这是因为你需要避免关于引用计数的数据竞争 |
共享对象的析构器不会在预期的时间执行 |
不使用相同的内置指针值初始化(或reset)多个智能指针 |
不delete get()返回的指针 |
不使用get()初始化或reset另一个智能指针 |
如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了 |
如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器 |
shared_ptr的类型转换不能使用一般的static_cast,这种方式进行的转换会导致转换后的指针无法再被shared_ptr对象正确的管理。应该使用专门用于shared_ptr类型转换的 static_pointer_cast<T>() , const_pointer_cast<T>() 和dynamic_pointer_cast<T>() |
下面我们看一下官网对shared_ptr详细的描述:
定义于头文件 <memory> | |
template< class T > class shared_ptr; | (C++11 起) |
std::shared_ptr是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。下列情况之一出现时销毁对象并解分配其内存:
-
最后剩下的占有对象的 shared_ptr 被销毁;
-
最后剩下的占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。
用 delete 表达式或在构造期间提供给 shared_ptr 的定制删除器销毁对象。
shared_ptr 能在存储指向一个对象的指针时共享另一对象的所有权。此特性能用于在占有其所属对象时,指向成员对象。存储的指针为 get() 、解引用及比较运算符所访问。被管理指针是在 use_count 抵达零时传递给删除器者。
shared_ptr 亦可不占有对象,该情况下称它为空 (empty) (空 shared_ptr 可拥有非空存储指针,若以别名使用构造函数创建它)。
shared_ptr 的所有特化满足可复制构造 (CopyConstructible) 、可复制赋值 (CopyAssignable) 和可小于比较 (LessThanComparable) 的要求并可按语境转换为 bool 。
多个线程能在 shared_ptr 的不同实例上调用所有成员函数(包含复制构造函数与复制赋值)而不附加同步,即使这些实例是副本,且共享同一对象的所有权。若多个执行线程访问同一 shared_ptr 而不同步,且任一线程使用 shared_ptr 的非 const 成员函数,则将出现数据竞争;原子函数的 shared_ptr 特化能用于避免数据竞争。
成员类型
成员类型 | 定义 | ||||
element_type |
| ||||
weak_type (C++17 起) | std::weak_ptr<T> |
成员函数
(构造函数) | 构造新的 shared_ptr (公开成员函数) |
(析构函数) | 如果没有更多 shared_ptr 指向持有的对象,则析构对象 (公开成员函数) |
operator= | 对 shared_ptr 赋值 (公开成员函数) |
修改器 | |
reset | 替换所管理的对象 (公开成员函数) |
swap | 交换所管理的对象 (公开成员函数) |
观察器 | |
get | 返回存储的指针 (公开成员函数) |
operator*operator-> | 解引用存储的指针 (公开成员函数) |
operator[](C++17) | 提供到被存储数组的带下标访问 (公开成员函数) |
use_count | 返回 shared_ptr 所指对象的引用计数 (公开成员函数) |
unique(C++20 前) | 检查所管理对象是否仅由当前 shared_ptr 的实例管理 (公开成员函数) |
operator bool | 检查是否有关联的管理对象 (公开成员函数) |
owner_before | 提供基于拥有者的共享指针排序 (公开成员函数) |
非成员函数
make_sharedmake_shared_for_overwrite(C++20) | 创建管理一个新对象的共享指针 (函数模板) |
allocate_sharedallocate_shared_for_overwrite(C++20) | 创建管理一个用分配器分配的新对象的共享指针 (函数模板) |
static_pointer_castdynamic_pointer_castconst_pointer_castreinterpret_pointer_cast(C++17) | 应用 static_cast、dynamic_cast、const_cast 或 reinterpret_cast 到被存储指针 (函数模板) |
get_deleter | 返回指定类型中的删除器,若其拥有 (函数模板) |
operator==operator!=operator<operator<=operator>operator>=operator<=> (C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20 中移除)(C++20) | 与另一个 shared_ptr 或 nullptr 进行比较 (函数模板) |
operator<< | 将存储的指针的值输出到输出流 (函数模板) |
std::swap(std::shared_ptr)(C++11) | 特化 std::swap 算法 (函数模板) |
std::atomic_is_lock_free(std::shared_ptr)std::atomic_load(std::shared_ptr)std::atomic_load_explicit(std::shared_ptr)std::atomic_store(std::shared_ptr)std::atomic_store_explicit(std::shared_ptr)std::atomic_exchange(std::shared_ptr)std::atomic_exchange_explicit(std::shared_ptr)std::atomic_compare_exchange_weak(std::shared_ptr)std::atomic_compare_exchange_strong(std::shared_ptr)std::atomic_compare_exchange_weak_explicit(std::shared_ptr)std::atomic_compare_exchange_strong_explicit(std::shared_ptr) (C++20 中弃用) | 特化的原子操作 (函数模板) |
辅助类
std::hash<std::shared_ptr> (C++11) | std::shared_ptr 的散列支持 (类模板特化) |
std::atomic<std::shared_ptr>(C++20) | 原子共享指针 (类模板特化) |
注意
只能通过复制构造或复制赋值其值给另一 shared_ptr ,将对象所有权与另一 shared_ptr 共享。用另一 shared_ptr 所占有的底层指针创建新的 shared_ptr 导致未定义行为。
std::shared_ptr 可以用于不完整类型 T 。然而,参数为裸指针的构造函数( template<class Y> shared_ptr(Y*) )和 template<class Y> void reset(Y*) 成员函数只可以用指向完整类型的指针调用(注意 std::unique_ptr 可以从指向不完整类型的裸指针构造)。
实现说明
在典型的实现中, std::shared_ptr 只保有二个指针:
-
get() 所返回的指针
-
指向控制块的指针
控制块是一个动态分配的对象,其中包含:
-
指向被管理对象的指针或被管理对象本身
-
删除器(类型擦除)
-
分配器(类型擦除)
-
占有被管理对象的 shared_ptr 的数量
-
涉及被管理对象的 weak_ptr 的数量
以调用 std::make_shared 或 std::allocate_shared 创建 shared_ptr 时,以单次分配创建控制块和被管理对象。被管理对象在控制块的数据成员中原位构造。通过 shared_ptr 构造函数之一创建 shared_ptr 时,被管理对象和控制块必须分离分配。此情况中,控制块存储指向被管理对象的指针。
shared_ptr 持有的指针是通过 get() 返回的;而控制块所持有的指针/对象则是最终引用计数归零时会被删除的那个。两者并不一定相等。
shared_ptr 的析构函数会将控制块中的 shared_ptr 计数器减一,如果减至零,控制块就会调用被管理对象的析构函数。但控制块本身直到 std::weak_ptr 计数器同样归零时才会释放。
既存实现中,若有共享指针指向同一控制块,则自增弱指针计数 ([1], [2]) 。
为满足线程安全要求,引用计数器典型地用等价于用 std::memory_order_relaxed 的 std::atomic::fetch_add 自增(自减要求更强的顺序,以安全销毁控制块)。
举个小例子:
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
struct Base
{
Base() { std::cout << " Base::Base()\n"; }
// 注意:此处非虚析构函数 OK
~Base() { std::cout << " Base::~Base()\n"; }
};
struct Derived: public Base
{
Derived() { std::cout << " Derived::Derived()\n"; }
~Derived() { std::cout << " Derived::~Derived()\n"; }
};
void thr(std::shared_ptr<Base> p)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count
{
static std::mutex io_mutex;
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << "local pointer in a thread:\n"
<< " lp.get() = " << lp.get()
<< ", lp.use_count() = " << lp.use_count() << '\n';
}
}
int main()
{
std::shared_ptr<Base> p = std::make_shared<Derived>();
std::cout << "Created a shared Derived (as a pointer to Base)\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
std::thread t1(thr, p), t2(thr, p), t3(thr, p);
p.reset(); // 从 main 释放所有权
std::cout << "Shared ownership between 3 threads and released\n"
<< "ownership from main:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
t1.join(); t2.join(); t3.join();
std::cout << "All threads completed, the last one deleted Derived\n";
}
可能的输出:
Base::Base()
Derived::Derived()
Created a shared Derived (as a pointer to Base)
p.get() = 0x2299b30, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
p.get() = 0, p.use_count() = 0
local pointer in a thread:
lp.get() = 0x2299b30, lp.use_count() = 5
local pointer in a thread:
lp.get() = 0x2299b30, lp.use_count() = 3
local pointer in a thread:
lp.get() = 0x2299b30, lp.use_count() = 2
Derived::~Derived()
Base::~Base()
All threads completed, the last one deleted Derived