看到这些问题,内心就发毛。什么是智能指针啊?为何要用智能指针啊?循环引用又是什么鬼?实现?我❌...
ios
常见的智能指针有几种,一种是共享指针shared_ptr,一种是独享指针unique_ptr,一种是弱指针weak_ptr,一种是好久没用过的auto_ptr(被unique_ptr替代了)。
智能指针也是指针,它也属于指针的范畴,可是它比通常的指针智能,说明了是智能指针,确定就智能一点。就像如今的智能家居跟普通家居的区别。那么它到底智能到什么程度呢?咱们知道,动态内存,也就是咱们日常在堆中申请内存的时候,要是咱们用了指针,以下
程序员
T *p = new T();
delete p;
复制代码
就须要进行管理,例如delete,delete和new
是一对操做,delete[]和new[]
他两都是一对cp出现,来虐程序员这群单身汪🐶。new这样的操做就是为对象分配空间并返回一个指向该对象的指针。因此返回了一个指针,那么咱们就须要管理这个指针,delete就是接受一个动态对象的指针,销毁该对象(调用析构函数,除了内置类型不作处理),并释放与之关联的内存。
你让一个单身🐶去管理?这不闹着玩吗?<-_<-! 在动态内存的使用,比较容易出现问题,一旦忘记了delete或者在不正确的时间作了不正确的事情,或者在不正确的时间作了正确的事情,那也是不正确,或者在正确的时间作了不正确的事情,就容易发生内存泄漏。哪能怎么办?
因此 标准库(记住是标准库提供的),定义在头文件<memory>
,提供了智能指针,这为了建设和谐美好社会,贡献了很多。
智能指针行为相似于常规的指针,这里只是相似,由于智能指针会负责自动释放所指向的对象。(其实就是让cp滚远点🐶🐶🐶)
并且指针并不能指出谁拥有了对象,不知道谁拥有全部权,可是智能指针却拥有全部权。
后端
unique_ptr<string> p1(new string("hi,world")); // 必须采用直接初始化的形式初始化
unique_ptr<string> p2(p1); // ❌ 不支持拷贝
unique_ptr<string> p3;
p3 = p2; // ❌ 不支持赋值
复制代码
上面的代码,是否是让unique_ptr感受很适然,个人东西是个人,你不能留副本,仅此一件。他表示的是互斥全部权。通常的指针都支持拷贝赋值操做,可是这里他就把拷贝构造函数和拷贝赋值运算符都delete
了,根本不让你拷贝。
安全
unique_ptr<string> p1(new string("hi"));
unique_ptr<string> p2(p1.release()); // 将p1置为空,返回指针
cout << *p2 << endl;
unique_ptr<string> p3(new string("hello,world"));
p2.reset(p3.release()); // reset释放了p2原来指向的内存 而后令p2指向p3所指向的对象,而后release()将p3置为空
cout << *p3 << endl; // 输出的都是hi
cout << *p1 << endl; // p1已经被释放了,没有了
复制代码
那么他那么多限制,在标准库的实现又是怎样的呢?来看看下面这一段ide
template<typename T,typename D = default_delete<T> > // default_delete是一个无状态类
class unique_ptr{
public:
using pointer = ptr;
using element_type = T;
using deleter_type = D;
constexpr unique_ptr() noexcept;
constexpr unique_ptr(nullptr_t) noexcept:unique_ptr(){} // 空指针类型
explicit unique_ptr(pointer p) noexcept; // from pointer
unique_ptr(pointer p,typename conditional<is_reference<D>::value,D,const D&> del) noexcept; // lvalue
unique_ptr(pointer p,typename remove_reference<D>::type&& del) noexcept; // rvalue
unique_ptr(unique_ptr&& x) noexcept;// 右值 移动构造函数
template<class U,class E>
unique_ptr(unique_ptr<U,E>&& x)noexcept; // 特例化
template<class U>
unique_ptr(auto_ptr<U>&& x)noexcept; // 不抛出异常
Unique_ptr(const unique_ptr&) = delete; // 不容许拷贝
unique_ptr& operator=(unique_ptr&& x) noexcept; // 移动赋值运算
unique_ptr& operator=(nullptr_t) noexcept; // 空指针类型
template<class U,class E>
unique_ptr& operator=(unique_ptr<U,E>&& x)noexcept; // 强制类型转换
unique_ptr& operator=(const unique_ptr&) = delete; // 不容许赋值
};
复制代码
为了避免抛出异常,都设置了noexpect,以上包括了右值引用操做,左值引用操做,以及某一构造函数特例化操做。基本囊括并且也解释了为何不拷贝不赋值。
release()和reset() 这两个函数都是将指针的全部权从一个(非const)unique_ptr转移给另外一个unique_ptr。
reset()还能好一点,能够释放内存,可是release()就不行了,release()必须有 接盘侠,接了要么能够自动负责释放,要么负责手动释放。
接下来咱们看看这两个的实现方式
函数
void reset(pointer p = pointer()) noexcept;
// 这里有一个默认值
pointer release() noexcept;
// 这里返回一个值
复制代码
release()是返回一个pointer,因此说它须要一个接盘侠。
ui
deleter_type& get_deleter() noexcept;
const deleter_type& get_deleter() const noexcept;
复制代码
#include <memory>
#include <iostream>
#include <string>
using namespace std;
class Role{
public:
Role(const string &crole):role(crole){
cout << role << endl;
}
~Role(){
cout << "delete" << endl;
}
void delRole(){
cout << "delete Role outside" << endl;
}
private:
string role;
};
void outdelRole(Role *r){
r->delRole();
}
int main(){
unique_ptr<Role,decltype(outdelRole)*> p1(new Role("trans"),outdelRole);
return 0;
}
复制代码
输出trans delete Role outside
这个🌰,充分说明了,咱们能够重载释放器,若是是函数的释放器,那么他的参数类型必须是一个objT类型的指针,这样才有删除的意义。decltype是通常用来指明类型的spa
unique_ptr<objT,delT>p(new objT,fcn); // fcn是delT类型对象
复制代码
这样你想怎么删,删什么就由你自个儿来定了。
也能够这样作线程
#include <iostream>
#include <memory>
using namespace std;
class state_deleter { // a deleter class with state
int count_;
public:
state_deleter() : count_(0) {}
template <class T> void operator()(T* p) {
cout << "[deleted #" << ++count_ << "]\n";
delete p;
}
};
state_deleter del;
unique_ptr<int,state_deleter> alpha (new int);
unique_ptr<int,state_deleter> beta (new int,alpha.get_deleter());
// gamma and delta share the deleter "del" (deleter type is a reference!):
unique_ptr<int,state_deleter&> gamma (new int,del);
unique_ptr<int,state_deleter&> delta (new int,gamma.get_deleter());
复制代码
再来看一段比较陷阱的代码设计
unique_ptr<string> p1;
cout << *p1 << endl;
复制代码
这段代码表明p1是一个空指针,那这个空指针,没有指向一个对象,那下面这一段呢?
unique_ptr<string> p1();
cout << *p1 << endl;
复制代码
输出的是1,为何呢?由于unique_ptr<string> p1()
声明一个无参函数p1,返回的类型是unique_ptr类型的指针,因此要是*p1,那只能是1,他是一个函数体
pointer get() const noexcept;
复制代码
get()是托管一个对象的指针或者空指针
unique_ptr<string> p1(new string("hello world"));
string *pstr = p1.get();
cout << *pstr << endl;
复制代码
他与release()不一样,它只是托管,get并是将pstr指向了p1指向的对象,可是并无释放p1的内存,pstr并无获取到这个智能指针的全部权,只是获得了它的对象。p1仍是须要在某个时刻删除托管数据pstr。
再来看一下解引用运算符
typename add_lvalue_reference<element_type>::type operator*() const;
复制代码
做用支持指针操做呗
unique_ptr<string> p1(new string("hello world"));
cout << *p1 << endl;
复制代码
再看看->运算符
pointer operator->()const noexcept;
复制代码
支持指针行为的操做
unique_ptr<C> foo (new C);
unique_ptr<C> bar;
foo->a = 10;
foo->b = 20;
bar = std::move(foo); // 支持右值移动操做 foo就释放了
复制代码
那好,咱们知道整个unique_ptr都会支持指针的行为,那咱们看看它的特例化版本。什么是特例化?就是对于特别的🌰进行特别的处理。不一样的版本
template<class T,class D> class unique_ptr<T[],D>;
复制代码
// 用于内置函数
unique_ptr<int[]> make_sequence(int n){
unique_ptr<int[]> p{new int[n]};
for(int i = 0;i<n;++i)
p[i] = i;
return p; // 返回局部对象
}
复制代码
这里固然要新增长独一[]运算符的做用,也就是重载 []运算符。
element_type& operator[](size_t i)const;
复制代码
不一样担忧匹配问题,咱们提供特例化版本只是帮编译器作了匹配的工做而已。
那交换指针?交换也是移动操做呀!
template <class T,class D> void(unique_ptr<T,D>& x,unique_ptr<T,D>& y)noexpect;
复制代码
交换两方的全部权,你要个人,我要你的。固然这是非成员函数,也有成员函数的写法
void swap(unique_ptr& x) noexcept;
复制代码
就是a.swap(b)
酱紫。
既然讲完了unique_ptr,那咱们就来说讲这个让社会更美好的shared_ptr,共享指针。
先来看看怎么用
shared_ptr<string> p1;
shared_ptr<list<int> > p2;
复制代码
经过默认初始化,p1和p2都是空指针。固然这两个操做,都没有分配和使用动态内存。要怎么作呢?咱们尝试这样。
shared_ptr<string> p1(new string("hehehe"));
cout << *p1 << endl;
复制代码
也能够试一下这样
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p));
}
复制代码
也能够管理内置指针inum
int *inum = new int(42);
shared_ptr<int> p2(inum);
复制代码
停🤚停🤚停🛑,先说明白,shared_ptr共享指针究竟是什么?
shared_ptr表示共享全部权,和unique_ptr指针不一样,shared_ptr能够共享一个对象。当两段代码须要访问同一个数据,但二者都没有独享全部权(负责销毁对象)时,可使用shared_ptr。shared_ptr是一种计数指针,当计数(use_count)变为0时释放所指向的对象。
能够理解为包含两个指针的结构,一个指针指向对象,另外一个指针指向计数器(use_count)。
而仅仅是由于当计数变为0才会销毁所指向的对象,它的释放器(deleter)与unique_ptr就不同,是一个非成员函数。可是是一个可调用对象,可调用对象后面我会专门去讲,可是在这里就要明白,shared_ptr的是释放器是 运行时绑定的,而不是 编译时就绑定的。而unique_ptr就是编译时绑定的释放器。默认的释放器是delete
,这个却没有变。(调用对象的析构函数并释放自由存储空间)
它的重点就在于使用计数上,那这个计数又是怎么定义的呢?来看一段代码。
shared_ptr<int> p3 = make_shared<int>(42);
cout << p3.use_count() << endl;
复制代码
看吧,这里的use_count()就是用来计数的,如今是1,就是这个对象引用了一次。
shared_ptr<int> p3 = make_shared<int>(42);
auto r = p3;
cout << p3.use_count() << endl;
复制代码
这里就是2了,这里会怎样,递增p3的引用计数,那r呢?r的计数是多少?r是2啊,这里就是说这个r也指向p3的对象了,那么这个计数器确定是同样的。要是r原来有指向的对象呢?那原来r的指向的对象的计数器也要递减,也不影响其余的指针。
因此其实区别就是,这些共享全部权的指针,都没有权利把对象杀死,他把杀对象的事情外包了出去。(不忍心啊!😖)。
因此,这么看来,由于有一个计数器,因此咱们能够说,shared_ptr自动销毁所管理的对象。也能够说,shared_ptr自动释放相关联的内存。
能够看一下这段代码,来看看动态内存中的使用
#include <iostream>
#include <memory>
#include <string>
#include <initializer_list>
#include <vector>
using namespace std;
class StrBlob{
public:
typedef vector<string>::size_type size_type;
StrBlob():data(make_shared<vector<string> >()){}
StrBlob(initializer_list<string> il):data(make_shared<vector<string> >(il)){} // 使用参数列表初始化vector
size_type size() const { return data->size();}
bool empty() const { return data->empty();}
void push_back(const string &t){return data->push_back(t);}
void pop_back();
string &front();
string &back();
private:
shared_ptr<vector<string> > data; // 共享同一个数据?
void check(size_type i,const string &msg) const;
};
复制代码
当咱们拷贝,赋值或销毁一个StrBlob对象的时候,这个shared_ptr的数据成员将会被拷贝、赋值和销毁。那么每一次都是安全的操做,自动释放。由于计数器,因此安全。
因此其实也不复杂,就是但愿咱们能够用shared_ptr进行管理动态内存的资源。这里我待会也会着重讲(RAII)
ok,看完了在动态内存的资源管理,那咱们熟知的动态内存是怎样的?是那对cp,就是new和delete。其实shared_ptr和new也能够一块儿用。
shared_ptr<double> p1; // shared_ptr 能够指向一个double
shared_ptr<int> p2(new int(42)); // p2指向一个值42的int 直接初始化形式
复制代码
咱们看构造函数
template<typename U>
class shared_ptr{
public:
using element_type = U;
constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t):shared_ptr(){} // 空对象
template <class U> explicit shared_ptr(U* p); // 显式构造 不存在隐式转换
template <class U,class D> shared_ptr(U* p,D del); // 添加释放器
template <class D> shared_ptr(nullptr_t p,D del); // 空指针的释放器
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);
shared_ptr(const shared_ptr& x) noexcept;
template<class U> shared_ptr(const shared_ptr<U>& x)noexcept;
template<class U> explicit shared_ptr(const weak_ptr<U>& x);
shared_ptr(shared_ptr&& x)(shared_ptr<U>&& x)noexcept; // 右值移动
template <class U> shared_ptr(auto_ptr<U>&& x);
template <class U,class D> shared_ptr(unique_ptr<U,D>&& x);// 得到独享指针的全部权
template <class U> shared_ptr(const shared_ptr<U>& x,element_type* p)noexcept;
};
复制代码
在构造函数中,接受指针参数的智能指针构造函数是explicit
,就是显式构造,而不是隐式转换。
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p));
}
复制代码
在primer中,建议 不要混合使用普通指针和智能指针,怎么才算是混合呢?咱们来看一下它给的🌰。
void process(shared_ptr<int> ptr){
// 使用ptr
}// ptr离开做用域,被销毁
复制代码
在这个🌰中,ptr是值传递,你们都知道,值传递会增长拷贝,构造等成本,因此ptr计数值至少为2,很公道,当process结束时,计数值不会变为0。因此局部变量ptr被销毁,ptr指向的内存也不会释放。(因此说使用引用会减小增长引用计数)
void process(shared_ptr<int>& ptr){
cout << ptr.use_count() << endl;
cout << *ptr << endl;
}
复制代码
当咱们使用值传递的时候,引用计数至少为2,可是使用引用传递,引用计数就不会递增
shared_ptr<int> p3 = make_shared<int>(42);
cout << p3.use_count() << endl;
// auto r = p3;
// cout << r.use_count() << endl;
process(p3);
cout << p3.use_count() << endl;
复制代码
使用引用计数,输出始终如一。
看来这个🌰只能作引用和值传递的,好像和混合使用普通指针和智能指针没啥搭边啊!
int *x(new int(9));
process(shared_ptr<int>(x));
int j = *x;
cout << j << endl;
复制代码
上面的🌰咱们使用的是值传递。嗯。这个🌰说明什么呢?可能不是很懂shared_ptr<int>(x)
这种骚操做,咱们来看一下这样会不会懂了一点
shared_ptr<int> ptr = shared_ptr<int>(new int(10));
复制代码
懂了吧。
shared_ptr<T> p(q);
复制代码
q是内置指针,p管理这个内置指针所指向的对象。q必须指向new分配的内存且可以转换为T*类型。
因此上上面的例子说明了,这两个混合着用,临时的shared_ptr会被销毁,那所指向的内存也会被释放。因此x估计还指向那个内存,可是,x已经不知不觉中变成空悬指针了。
其实当讲一个shared_ptr绑定到一个普通指针时,咱们就将内存的管理责任交给了这位不知名的shared_ptr。因此,咱们就不能或者不该该再使用内置指针访问shared_ptr所指向的内存。
primer也建议 不要使用get初始化另外一个智能指针或为智能指针赋值。
get()函数上面也有简略的介绍,它的做用是,它返回一个内置指针,指向智能指针管理的对象。它的设计是为了在须要向不能使用智能指针的代码传递一个内置指针。什么意思?它只是一个托管指针。来看看这段代码
shared_ptr<int> p(new int(42));
int *q = p.get();
{
// 两个独立的shared_ptr指向相同的内存
shared_ptr<int>(q);
// 离开做用域就会释放
}
int foo = *q; // 最后未定义
复制代码
因此这里解释了不能用get()这样的初始化另外一个智能指针,get()毕竟是托管,给你的都是已经有的,托管而已,给了你,你也是指向相同的内存。
固然,shared_ptr也可使用reset操做
string *inum = new string("hhh");
shared_ptr<string> p5 = make_shared<string>("hi");
p5.reset(inum);
复制代码
可是他只能用于内置指针传递。
还能传递释放器给shared_ptr p5.reset(inum,d);
那为何shared_ptr没有release成员? 没有全部权呗。 讲了那么多,make_shared一直都像是被忽略了。
template <class T,class ... Args> shared_ptr<T> make_shared(Args&&... args);
复制代码
这是它的源码,他的用途就是制做shared_ptr
,返回类型为shared_ptr<T>
的对象,该对象拥有并存储指向它的指针(引用次数为1)。
看看怎么使用
auto baz =make_shared<pair<int,int> > (30,40);
... baz->first .. << baz->second
复制代码
ok,因此,当咱们使用shared_ptr
初始化的时候,最好最安全就是使用这个标准库函数,而且使用new确定还要转换啊,给予全部权,可是make_shared
帮你将分配,安全都作好了,并且给你的就是返回的shared_ptr
的类型对象,让你的指针指向就好了。
推荐使用哦!
shared_ptr,其实就是一个指针,套上了释放器,套上了计数器,拷贝的时候增长了引用,赋值也增长了引用,相应的也会有递减了引用计数。咱们再来看另一种状况
struct Node{
shared_ptr<Node> pPre;
shared_ptr<Node> pNext;
int val;
};
void func(){
shared_ptr<Node> p1(new Node());
shared_ptr<Node> p2(new Node());
cout << p1.use_count() << endl;
cout <<p2.use_count() << endl;
p1->pNext = p2;
p2->pPre = p1;
cout << p1.use_count() << endl;
cout <<p2.use_count() << endl;
}
复制代码
咱们看到,p1是2,p2也是2,他们互相拷贝引用啊!要想释放p2就要先释放p1,而要想释放p1,就得释放p2,这样就是 循环引用了,最后p1和p2指向的内存空间永远都没法释放掉。
那可咋办咧,上面介绍的居然没有一种能解决,不要慌,不要忙,静静在两旁。
静静往下看看。
上面这个就是一个环,咱们怎样打破这个环,让内存释放呢?使用weak_ptr
。介绍一下weak_ptr
,一种不控制所指向对象生存期的智能指针,指向由一个shared_ptr
管理的对象。看来这也是共享全部权的乐趣,众人帮,不像unique_ptr
,一我的孤苦伶仃。
不控制是什么意思?就是weak_ptr
,不影响shared_ptr
的引用计数。一旦shared_ptr
被销毁,那么对象也会被销毁,即便weak_ptr
还指向这个对象,这个对象也会被销毁。因此说,该走的仍是让你走。
因此它也叫作"弱"共享全部权。
只引用,不计数,可是有没有,要检查expired()
应运而生。
咱们来看一下他的构造以及使用
template <class T> class weak_ptr{
public:
constexpr weak_ptr() noexcept;
weak_ptr(const weak_ptr& x) noexcept;
template <class U> weak_ptr(const weak_ptr<U>& x) noexcept;
template <class U> weak_ptr(const shared_ptr<U>& x) noexcept;
}
复制代码
因此从构造函数能够看出,这个weak_ptr
,能够本身构造,也能够指向share_ptr
,并且仅仅是引用。
shared_ptr<int> sp(new int(42));
weak_ptr<int> wp(sp);
cout << wp.use_count << endl;
复制代码
那use_count呢?
long int use_count() const noexcept;
复制代码
看到了嘛。它并不会改变引用计数。const
那expired是什么? 它只是检查use_count()是否是变为0了,为0返回false,不然返回true。
bool expired() const noexcept;
复制代码
这是用来检查一下这个指针所指向的对象是否被销毁了。
因此这就致使对象可能就不存在,所以咱们不能使用weak_ptr直接访问对象,何况weak_ptr也没有*
这个访问运算符重载的过程,就须要调用别的函数,例如lock
shared_ptr<T> lock() const noexcept;
复制代码
lock() 会检查weak_ptr
所指向的对象是否存在,若是存在就返回一个共享对象shared_ptr。
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> sp1,sp2;
std::weak_ptr<int> wp;
// sharing group:
// --------------
sp1 = std::make_shared<int> (20); // sp1
wp = sp1; // sp1, wp
sp2 = wp.lock(); // sp1, wp, sp2
sp1.reset(); // wp, sp2
sp1 = wp.lock(); // sp1, wp, sp2
std::cout << "*sp1: " << *sp1 << '\n';
std::cout << "*sp2: " << *sp2 << '\n';
return 0;
}
复制代码
很清楚,都输出20。一样,reset就能置空一个weak_ptr
那么为何,weak_ptr能破环呢?咱们继续来看下面这一段代码
struct Node{
weak_ptr<Node> pPre; // 区别⬅️⬅️⬅️
weak_ptr<Node> pNext; // 区别⬅️⬅️⬅️
int val;
Node(){
cout << "construct" << endl;
}
~Node(){
cout << "delete" <<endl;
}
};
void func(){
shared_ptr<Node> p1(new Node());
shared_ptr<Node> p2(new Node());
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1->pNext = p2;
p2->pPre = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
}
复制代码
这就打破了循环引用的环,由于每个shared_ptr都会将引用计数设为1,那么每次用都会递增,因此要是不递增,用原来的指向的对象不就解决了嘛。改一下结构就完美解决,并且还能调用了析构函数。
讲完了weak_ptr,忽然感受,智能指针的发明确实伟大!单身🐶迷茫的时候容易犯的错误变得再也不容易。那么,每次咱们都会发现,这两个指针,会有一个释放器。
unique_ptr版本
unique_ptr<T,D> up;
复制代码
shared_ptr版本
shared_ptr<T> p(q,d);
复制代码
无论大小写的d都是delete,释放器。向unique_ptr咱们以前介绍过,这是一个肯定的删除器,在编译时就已经决定了它的类型了。
unique_ptr
template<typename T,typename D = default_delete<T> > // default_delete是一个无状态类
class unique_ptr{
public:
using pointer = ptr;
using element_type = T;
using deleter_type = D;
...
复制代码
那shared_ptr咧
template<typename U>
class shared_ptr{
public:
using element_type = U;
constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t):shared_ptr(){} // 空对象
template <class U> explicit shared_ptr(U* p); // 显式构造 不存在隐式转换
...
复制代码
看到这个template就明白,原来shared_ptr一直没有固定类型的释放器,虽然默认是delete,可是也可使用可调用对象,看看下面这个可调用对象的例子
#include <iostream>
#include <memory>
int main () {
auto deleter = [](Node* p){
cout << "[deleter called]\n";
delete p;
};
// shared_ptr<int> foo (new int,deleter);
// cout << "use_count: " << foo.use_count() << '\n';
shared_ptr<Node> bar(new Node(),deleter);
return 0; // [deleter called]
}
复制代码
因此释放器,不管是unique_ptr
仍是shared_ptr
都必须保存为一个指针或一个封装了指针的类。但咱们也能够肯定,shared_ptr
不是将释放器直接保存为一个成员,由于它的类型直到运行时才知道。
由于shared_ptr
只有一个模版参数,而unique_ptr
有两个模版参数,因此在这个unique_ptr
的工做方式,咱们能够看出来,这个释放器的类型是unique_ptr
类型的一部分,因此释放器能够直接保存在unique_ptr
对象中。
两个释放器都是对其保存的指针调用用户提供提供的释放器或执行delete
。
因此,总结一下,经过编译时绑定释放器,unique_ptr
避免了间接调用释放器的运行时开销。
经过运行时绑定释放器,shared_ptr
使用户重载释放器更加方便。
因此这些都是以对象来管理资源的例子,一个一个shared_ptr,unique_ptr
都在以对象的形式管理着资源,防止资源的泄露,动态内存不再用惧怕泄漏了。
额,那可调用对象又有哪些呢?怎么用呢?为何shared_ptr
能够这样用可调用对象呢?
发布了这篇文章,但愿后端大牛们随便喷,小弟定当改进😊。
另: 写文不易,转载请标明出处