Th5.5:智能指针(shared_ptr)之使用场景、陷阱、性能分析、使用建议详述

本小节回顾学习的知识点分别是shared_ptr之、陷阱、性能分析、使用建议的详述。

今天总结的知识分为以下3个大点:
(1)std::shared_ptr使用陷阱分析
    (1.1)慎用裸指针来构造shared_ptr
    (1.2)慎用get()返回的指针
    (1.3)不要把类对象指针(this)作为shared_ptr返回,改用
enable_shared_from_this;
(2)性能说明
    (2.1)尺寸问题
    (2.2)移动语义
(3)补充说明和使用建议

(1)std::shared_ptr的使用陷阱分析:

        As we all know,shared_ptr会帮助我们自动管理在堆区heap中开辟的内存。非常好用,但正所谓万事万物有利必有弊!一旦用错shared_ptr的话,也会造成致命的后果!

    (1.1)慎用裸指针来构造shared_ptr:

        慎用理由1:凡是使用了裸指针来构造shared_ptr智能指针时,那么在后续的代码中。就不要再使用该裸指针了。因为会造成不安全的case!

例如以下场景:

void func(shared_ptr<int> ptr) {
	return;
}
int* p = new int(100);//裸指针
func(shared_ptr<int>(p));
//这里传入func函数中的参数是一个临时的shared_ptr
//我用一个裸指针显式地构造这个临时的shared_ptr

//但是,这个裸指针p的内存在传入func后被释放了
//此时你无法对它的内存空间do事情了!
*p = 45;//×!此时p的内存空间已经被释放了!

调试结果:

        裸指针p在传入func函数之前:

(p的地址:)

        裸指针p在传入func函数并退出该函数之后: 

(p的地址:)

 下面这样使用裸指针来创建shared_ptr对象才是正确的做法:

void func(shared_ptr<int> ptr) {
	return;
}
int* p = new int(100);//裸指针
shared_ptr<int> p2(p);//把裸指针绑定到shared_ptr上
func(p2);//传入一个shared_ptr指针,即:我把内存交给p2管理了!很好!
*p2 = 188;//ok!没问题的!安全的!

        or

void func(shared_ptr<int> ptr) {
	return;
}
shared_ptr<int> p2(new int(100));//把裸指针绑定到shared_ptr上
func(p2);//传入一个shared_ptr指针,即:我把内存交给p2管理了!很好!
*p2 = 188;//ok!没问题的!安全的!

        慎用理由2:使用裸指针构造shared_ptr时候,一个裸指针只能绑定到一个shared_ptr中不能绑定到多个shared_ptr中!

为什么呢?下面不妨请看以下代码:

int* p = new int(100);//裸指针
shared_ptr<int> p1(p);//把裸指针p绑定到p1上
shared_ptr<int> p2(p);//把裸指针p绑定到p2上
p1.reset();//释放shared_ptr指针p1

运行结果:

        在执行p1.reset()这行代码之前的调试运行结果:

         

         在执行p1.reset()这行代码之后的调试运行结果:

  

        从调试结果可见,当一个裸指针被绑定到多个shared_ptr智能指针上时,就会出现一旦某个智能指针被释放时,别的绑定到该裸指针上的智能指针就会指向一个乱码的对象。(因为你已经释放了裸指针p了,p所指向的内存别的指向它的shared_ptr指针没有权限访问了)此外,如果别的指向裸指针的shared_ptr指针释放时,还会导致另外一个问题:重复释放同一内存空间。这会导致你的程序产生异常

        综上所述,慎用裸指针绑定到shared_ptr指针上!!!

    (1.2)慎用get()返回的指针:

        .get()函数都作用:返回智能指针指向的对象所对应的裸指针。

(因为有些库函数需要你传入的是裸指针而不是智能指针所以C++11才引入shared_ptr的这种方法!)

请看以下代码:

shared_ptr<int> p1(new int());
shared_ptr<int> p2(p1);
auto pp = p1.get();

调试结果: 

        慎用理由:对shared_ptr智能指针用.get()方法得到的裸指针你千万不能随意delete!因为这个权限你要交给shared_ptr去管理!(若你随意delete后,shared_ptr就没办法帮助我们正常管理该内存了)

请看以下代码:

shared_ptr<int> p1(new int());
shared_ptr<int> p2(p1);
auto pp = p1.get();
delete pp;//错误!你delete了,那还用智能指针shared_ptr帮你管理内存干嘛?
shared_ptr<int> p1(new int());
auto pp = p1.get();

{
		shared_ptr<int> p3(pp); 
}
//相当于将裸指针pp既绑定到shared_ptr指针p1上,也绑定到p3上了
//这和前面的裸指针被绑定到多个shared_ptr指针上产生异常的case是一样的!

调试运行结果:

        在{shared_ptr<int> p3(pp); }这个大括号语句内的代码执行之前的调试结果:

(pp的地址:)

        在{shared_ptr<int> p3(pp); }这个大括号语句内的代码执行之后的调试结果:

(pp的地址:)

        从调试结果可见,当执行完{shared_ptr<int> p3(pp); }codes之后pp的内存就已经给释放掉了,当后续shared_ptr指针p1再自动帮我们释放内存时又会产生重复delete的异常问题

        综上所述,永远不要将shared_ptr中的方法.get()得到的裸指针绑定(赋值给/初始化给)到别的shared_ptr指针上!!!

    (1.3)不要把类对象指针(this指针)作为shared_ptr返回,改用enable_shared_from_this:

        当你在类中将this指针绑定给shared_ptr时,又在类的外部将该shared_ptr给别的shared_ptr指针做初始化的话,就又会造成上述使用裸指针时出现的问题:用一个裸指针绑定到多个shared_ptr上时,会造成重复释放用一份内存空间的异常问题!

请看以下代码:

class CT {
public:
	shared_ptr<CT>getself() {
		return shared_ptr<CT>(this);//<===> 用裸指针初始化了多个shared_ptr!
	}
};
shared_ptr<CT> pct1(new CT());
shared_ptr<CT> pct2 = pct1->getself();

运行结果:

那么如何解决在类中用this指针给shared_ptr初始化时的这个问题呢? 

答:用C++标准库里面的类模板:enable_shared_from_this来deal这个问题。

请看以下代码:

class CT:public enable_shared_from_this<CT> {
public:
	shared_ptr<CT>getself() {
		return shared_from_this();
		//通过模板类enable_shared_from_this中的方法shared_from_this将this指针用作
		//shared_ptr指针来do返回!
	}
};
shared_ptr<CT> pct1(new CT());
shared_ptr<CT> pct2 = pct1->getself();

正常运行~

(2)性能说明:
    (2.1)尺寸(占据的内存)问题:

        shared_ptr的尺寸是裸指针的2倍,weak_ptr尺寸也是裸指针的2倍。与weak_ptr一样,shared_ptr也是含有2个裸指针的。

a)第一个裸指针指向的是这个智能指针所指向的对象

b)第二个裸指针指向的是一个很大的数据结构-->控制块(由make_shared()函数来创建),这个控制块内含有:

        b.1)所指向对象的强引用计数

        b.2)所指向对象的弱引用计数

        b.3)其他data是:删除器指针、内存分配器等等。

    (2.2)移动语义:

        用std::move()函数do移动赋值,可直接让别的对象的内存访问权限直接给到我要do移动的对象。

请看以下代码:

shared_ptr<int> sp1(new int(1));
shared_ptr<int> sp2(std::move(sp1));
//移动语义:移动构造一个新的智能指针对象
//此时,sp1就不再指向该int型的val==1的对象了(变为空)
//引用计数依旧为1
shared_ptr<int> sp3(std::move(sp2));
//此时sp2就变为空,但其引用计数仍然为1
//用std::move()函数移动赋值肯定比单纯的赋值块:因为单纯的赋值时你还需要增加引用计数,但是移动则不需要!

(3)补充说明和使用建议:

        关于shared_ptr的知识我上面总结的差不多了。还有一个分配器的概念没总结。

分配器:解决内存分配的问题。

shared_ptr<int> p(new int(),mydeleter(),mymallocator<int>());...

这里就不详细讲了,因为分配器用得地方很少!!!

        建议:虽然shared_ptr<T>智能指针能帮助我们管理资源,但一定要细心地使用,谨慎地使用!虽然make_shared<T>()函数不能用自定义的删除器,但我们仍要优先使用make_shared<T>()函数来创建shared_ptr<T>智能指针!

        注意:即便是C++17标准之后,shared_ptr也无法配合make_shared来do动态分配数组的工作,但是unique_ptr已经可以配合make_unique或者new来do动态分配数组的工作了!

const int N = 5;

//这3行codes完全没有问题,可编译通过并正常运行!
unique_ptr<int[]> unpt = make_unique<int[]>(N);
for (int i = 0; i < N; i++) unpt[i] = i;
for (auto i = 0; i < N; i++)cout << unpt[i] << "\t";

//这3行codes看起来也没有问题,但就是编译不能通过!更别说正常运行了!
shared_ptr<int[]> spt = make_shared<int[]>(N);
for (int i = 0; i < N; i++) spt[i] = i;
for (auto i = 0; i < N; i++)cout << spt[i] << "\t";

unique_ptr这3行codes的运行结果:

shared_ptr这3行codes的运行结果:

        可见,shared_ptr就目前而言还是有缺陷的!当然,既然是共享式的smartPointer,那么它指向一个数组后,这个数组已经可以存放很多data了,没有必要再让别的同该类的shared_ptr再执行该数组了,王老师他个人认为这应该是C++标准委员会目前不让shared_ptr拥有这种能力的缘故吧!而unique_ptr则无此顾虑, 因为unique_ptr本来就是独占式的,赋初值后别人“碰都别想碰”!

        以上就是我总结的关于shared_ptr之、陷阱、性能分析、使用建议的详述的笔记。希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fanfan21ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值