shared_ptr常用操作、计数、自定义删除器 [转]

shared_ptr常用操作、计数、自定义删除器 [转]

参考:《new、delete探秘,智能指针概述、shared_ptr》《shared_ptr常用操作、计数、自定义删除器等等

目录

shared_ptr常用操作、计数、自定义删除器 [转]

shared_ptr基础

常规初始化(shared_ptr和new配合)

make_shared函数

shared_ptr引用计数的增加和减少

引用计数的增加

引用计数的减少

shared_ptr指针常用操作

use_cout()

unique()

reset()

*解引用

get()

swap()

= nullptr

智能指针名字作为判断条件

指定删除器以及数组问

  • 引例:
int *p11 = new int();
int *q11 = p11;
int *r11 = q11; 
  • 只有p11 q11 r11 都不在使用了的时候,才能释放掉这段内存。
  • new /delete的写法要非常小心,防止早早的释放,也防止忘记释放,总之:用好并不容易。
  • p 裸指针:直接用new 返回的指针,这种指针,强大,灵活,但是开发者全程负责维护,一个不小心,就容易用错,一旦用错,造成
  • 智能指针: 解决裸指针可能出现的各种问题;
  • 智能指针,就理解成对“裸指针”进行了包装,给裸指针外边包了一层;包装后为我们带来优点;
  • 最突出的优点: 智能指针能够“自动释放所指向的对象内存”,大家再也不用担心自己new出来的内存忘记释放了;
  • 建议优先选择智能指针,使用智能指针的程序更容易编写和调试;
  • C++ 标准库有四种智能指针:
    • auto_ptr (C++ 98);   unique_ptr (C++ 11);  shared_ptr (C++ 11); weak_ptr(C++ 11); 
  • 帮助我们进行动态分配对象(new出来的对象)的生命周期的管理。能够有效防止内存泄漏;
  • 目前auto_ptr已经完全被unique_ptr取代,所以大家不要再使用auto_ptr;C++ 11标准中反对使用auto_ptr(弃用)
  • 这三种智能指针都是类模板,我们可以将new获得地址赋给他们;
  • shared_ptr : 共享式指针。多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放。
  • weak_ptr 是辅助shared_ptr工作的;
  • unique_ptr : 独占式指针;同一个时间内,只有一个指针能够指向该对象。当然,该对象的所有权还是可以移交出去的;
  • 你忘记delete 的时候,智能指针帮组你delete,或者说,你压根就不再需要自己delete;智能指针的本分(帮助你delete)

shared_ptr基础

  • 共享所有权,不是被一个shared_ptr拥有,而是被多个shared_ptr之间互相协作;shared_ptr有额外开销;
  • 工作原理:引用计数,每个shared_ptr的拷贝都指向相同的内存。
  • 所以,只有最后一个指向该内存(对象)的shared_ptr指针不需要再指向该对象时,那么这个shared_ptr才会去析构所指向的对象
  • 最后一个指向该内存对象的shared_ptr在什么情况下会释放该对象(shared_ptr所指向的对象)呢?
    • 这个shared_ptr被析构的时候;
    • 这个shared_ptr指向其他的对象时
  • 垃圾回收机制;我们从此不用担心对象何时被delete;
  • 类模板,用到<>, <>里,就是指针可以指向的类型,后边再跟智能指针名;
  • 格式: shared_ptr<指向的类型> 智能指针名

常规初始化(shared_ptr和new配合)

shared_ptr<int> pi11(new int(100)); // pi指向一个值为100 的int型数据
  • 智能指针是explicit, 不可以进行隐式类型转换。必须用直接初始化形式;(“=” 一般都是隐式转换)
shared_ptr<int> pi211 = new int(200); //报错
  • 自定义生成shared_ptr函数
shared_ptr<int> makes(int value){
	//return new int(value);  不可以,无法把new得到的int * 转换成shared_ptr
	return shared_ptr<int>(new int(value)); //可以,显示的用int* 创建shared_ptr<int>

}
shared_ptr<int> pi311 = makes(130);
  • 裸指针可以初始化shared_ptr, 但不推荐。智能指针和裸指针不要穿插用
    • 下面这种写法不推荐
int *pi22 = new int;
shared_ptr<int> pl(pi22);
  • 建议写法:
shared_ptr<int> pl33(new int);

make_shared函数

  • 标准库里的函数模板,安全、搞笑的分配和使用shared_ptr;
  • 它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared_ptr;
shared_ptr<int> p7 = make_shared<int>(100); //这个shared_ptr指向一个值为100的整形内存,有点类似int *pi = new int(100)

shared_ptr<string> p8 = make_shared<string>(5, 'a'); // 5个字符a生成的字符串。
//类似于 string mystr(5, 'a');

shared_ptr<int> p9 = make_shared<int>(); // p4指向一个int, int里保存的值是0 (值初始化)
p9 = make_shared<int>(400); // p4 指向一个新int, int 里保存的是400; p4 首先释放刚才指向的值为0的内存,然后指向这个值为400的内存

auto p10 = make_shared<string>(5, 'a'); // 用auto比较简单

shared_ptr引用计数的增加和减少

  • 共享式,引用计数,每一个shared_ptr的拷贝都指向相同的内存(对象),只有最后一个指向该对象的shared_ptr指针不需要再指向该对象的时候,这个shared_ptr才会去析构所指向的对象。

引用计数的增加

  • 每个shared_ptr 都回记录有多少个其他的shared_ptr指向相同的对象;
auto p6 = make_shared<int>(100); // 目前p6所指向的对象只有p6一个引用者;
auto p7(p6); //智能指针定义时的初始化,p7和p6指向了相同的对象,此对象目前有两个引用者;
  • 在如下情况下,所有指向这个对象的shared_ptr引用计数都会增加1
    • a)向上边这样,我们用p6来初始化p7这个智能指针;
    • b)把智能指针当做实参往函数里传递;
void myfunc(shared_ptr<int> ptmp){
	return;
}
  • myfunc(p7); 
  • 如果改成传引用就不会增加引用计数。 void myfunc(shared_ptr<int> &ptmp)
  • c)作为函数的返回值
shared_ptr<int> myfunc1(shared_ptr<int>& ptmp){ // 如果参数为引用,则智能指针的引用计数不会增加
	return ptmp;
}
auto p6 = make_shared<int>(100); 
auto p7(p6); 
auto p8 = myfunc1(p7); // 引用计数会变成3,这是因为有p8来接这个临时的智能指针。
myfunc(p7); // 没有变量来接这个临时的智能指针,则临时智能指针生命周期到了,引用计数从3恢复到2;

引用计数的减少

  • a). 给shared_ptr赋予新值,让该shared_ptr指向一个新对象;
auto p6 = make_shared<int>(100);  
auto p7(p6); 
auto p8 = myfunc1(p7);
p8 = make_shared<int>(200); // p8 指向新对象,计数为1, p6, p7 指向的对象计数恢复为2;
p7 = make_shared<int>(300); // p7 指向新对象,计数为1, p6 指向的对象计数恢复为1;
p6 = make_shared<int>(400); // p6 指向新对象,计数为1, p6指向的原内存被释放;
  • b). 局部的shared_ptr离开其作用域;
auto p9 = make_shared<int>(100); // 目前p9所指向的对象只有p9一个引用者;
auto p10(p9); // p9 和 p10 指向相同的对象,引用计数为2;
myfunc(p10);// 进入函数体myfunc中是3个引用计数,从myfunc中退出时引用计数恢复为2个;
  • c). 当一个shared_ptr引用计数从1变成0,则它会自动释放自己所管理
auto p11 = make_shared<int>(100); // 只有p11指向该对象
auto p12 = make_shared<int>(100); 
p11  = p12;
// 给p11赋值会让p11指向p12所指向的对象,该对象引用计数变成2,
//而原来p11所指向的对象引用计数会从1变成0,从而导致被释放。

shared_ptr指针常用操作

use_cout()

  • 返回多少个智能指针指向某个对象,主要用于调试目的;
shared_ptr<int> myp(new int(100));
int icout = myp.use_count(); // 1
shared_ptr<int> myp2(myp);
icout = myp.use_count(); // 2

shared_ptr<int> myp3;
myp3 = myp2;
icout = myp3.use_count(); // 3
icout = myp.use_count(); // 3

unique()

  • 是否该智能指针独占某个指向的对象,也就是若只有一个智能指针指向某个对象,则unique()返回true,否则返回false;
shared_ptr<int> myp4(new int(100));
shared_ptr<int> myp5(myp4);
if (myp4.unique()) {  
	cout << "unique OK" << endl;
}
else{
	cout << "unique NO NO NO" << endl;
}

shared_ptr<int> myp6;
if (myp6.unique()) { //成立
	cout << "unique OK " << endl;
}

reset()

  • 恢复(复位/重置)的意思
  • a)reset()不带参数时
  • 若pi 是唯一指向该对象的指针,那么释放pi所指向的对象,并将pi置空
shared_ptr<int> pi(new int(100));
pi.reset();
if (pi == nullptr)
{
	cout << "pi 被置空" << endl;
}
  • 若pi不是唯一指向该对象的指针,那么不释放pi所指向的对象,但指向该对象的引用计数会减少1,同时将pi置空
shared_ptr<int> pi1(new int(100));
auto pi2(pi1);
pi1.reset();
  • b)reset()带参数(一般是一个new出来的指针)时
    • 若pi1是唯一指向该对象的指针,则释放pi1指向的对象,让pi1指向新对象。
    • 若pi1不是唯一指向该对象的指针,则不释放pi1指向的对象,但指向该对象的引用计数会减少1,同时让pi1指向新对象;
shared_ptr<int> pi3(new int(100));
auto pi4(pi3); // pi4引用计数会变成2
pi3.reset(new int(1)); //释放原内存,指向新内存;
if (pi3.unique()){
	cout << "pi3 unique OK" << endl;
}

if (pi4.unique()) {
	cout << "pi4 unique OK" << endl;
}
  • 空指针也可以通过reset来重新初始化
shared_ptr<int> p;
p.reset(new int(1)); 
  • 释放p所指向的对象,让p指向新对象,因为原来p为空的,所以就等于啥也没释放直接指向新对象;

*解引用

  • 获得p指向的对象
shared_ptr<int> pother(new int(123456));
cout << *pother << endl;

get()

  • 考虑到有些函数(第三方函数)的参数需要的是一个内置裸指针而不是智能指针。
  • p.get() : 返回p中保存的指针(裸指针),小心使用,如果智能指针释放了所指向的对象,那么这个返回的裸指针也就变得无效了;
shared_ptr<int> myp7(new int(100));
int *p111 = myp7.get();
*p111 = 45;

delete p111; //千万不要这么干,不然系统会报告异常,产生不可预料的结果;

swap()

  • 交换两个智能指针所指向的对象
shared_ptr<string> ps1(new string("I Love China1!"));
shared_ptr<string> ps2(new string("I Love China2!"));
std::swap(ps1, ps2);
ps1.swap(ps2);

= nullptr

  • a)将所指向的对象引用计数减1,若引用计数变为0, 则释放智能指针所指向的对象。
  • b) 将智能指针置空
shared_ptr<string> ps3(new string("I Love China!"));
shared_ptr<string> ps4(ps3); //
ps3 = nullptr;

智能指针名字作为判断条件

shared_ptr<string> ps5(new string("I Love China!"));
ps5 = nullptr;
if (ps5){
	cout << "ps3指向一个对象" << endl;
}
else{
	cout << "ps1为空" << endl;
}

指定删除器以及数组问题

  • a). 指定删除器;
  • 一定时机帮我们删除所指向的对象,delete : 将delete运算符号作为默认的资源析构方式。
  • 我们可以指定自己的删除器取代系统提供的默认删除器,当智能指针需要删除所指向的对象时,编译器就会调用我们自己提供的删除器来删除。
  • shared_ptr指定删除器方法比较简单,一般只需要在参数中添加具体的删除器函数名即可;
void myDelete(int* p){ //我们的删除器,删除整形指针用的,当智能指针引用计数为0, 就会自动调用该删除器来删除对象。
	//写一些日志
	delete p; //既然你自己提供了删除器来取代智能指针的缺省删除器,你就有义务自己来删除所指向的对象。(引用计数为0时)
}
shared_ptr<int> p13(new int(123456), myDelete);
shared_ptr<int> p14(p13); // 两个引用计数
p14.reset(); // 剩1个引用计数,p14为 nullptr
p13.reset(); // 因为此时只有1个引用计数,所以要释放指向的对象,调用我们的删除器myDelete,同时p置空;
  • 删除器可以是一个lambda表达式:
shared_ptr<int> ps(new int(123456), [](int *p){
	delete p;
});
  • 有些情况,默认删除器处理不了,(用shared_ptr管理动态数组),需要我们提供自己指定的删除器;
shared_ptr<int> p22(new int[10], [](int *p){
	delete[]p;
});
class A{
public:
	A(){};
	~A(){};
};


shared_ptr<A> pA33(new A[10]);
 // 异常因为系统释放pA是delete pA所指向的裸指针,而不delete []pA
 //这种情况下我们需要写自己的删除器。
shared_ptr<A> pA44(new A[10], [](A *p){
	delete[] p;
});
  • 可用default_delete来做删除器,default_delete是标准库里的模板类。
shared_ptr<A> pA(new A[10], std::default_delete<A[]>());
  • 定义数组的时候我们在尖括号中加[]
shared_ptr<A[]> pA55(new A[10]); // 在<>中加个[]就行了;
shared_ptr<int[]> p77(new int[10]);
//p77[0] = 12;
//p77[1] = 15;
  • 或者自行封装
// 写个函数模板来封装shared_ptr数组
template<typename T>
shared_ptr<T> make_shared_array(size_t size){
	return shared_ptr<T>(new T[size], default_delete<T[]>());
}


shared_ptr<int> pintArr = make_shared_array<int>(5);
shared_ptr<A> pAArr = make_shared_array<A>(15);
  • b) 指定删除器额外说明
    • 就算是两个shared_ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared_ptr也属于同一类型;
auto lambda1 = [](int *p){
	//日志。。
	delete p;
};

auto lambda2 = [](int *p){
	//日志。。。
	delete p;
};
shared_ptr<int> p99(new int(100), lambda1);
shared_ptr<int> p100(new int(200), lambda2);
p100 = p99; // p100 会先调用lambda2 把自己所指向的对象释放,然后指向p99所指向的对象。p99指向的对象引用计数为2;
	//整个main执行完毕后,还会调用lambda1来释放p99、p100共同指向的对象;

p99.reset();
  • 类型相同,就代表可以放到元素类型为该对象类型的容器里来;
vector<shared_ptr<int>> pvec{ p99, p100 };
  • make_shared是提倡的生成shared_ptr的方法。但是make_shared这种方法,让我们没有办法指定自己的删除器;
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值