学习笔记:shared_ptr陷阱

条款1:不要把一个原生指针给多个shared_ptr管理

int* ptr = new int;

shared_ptr<int> p1(ptr);

shared_ptr<int> p2(ptr); //logic error

ptr对象被删除了2次

这种问题比喻成“二龙治水”,在原生指针中也同样可能发生。

 

条款2:不要把this指针给shared_ptr

class Test{

public:

    void Do(){  m_sp =  shared_ptr<Test>(this);  }

private:

    shared_ptr<Test> m_member_sp;

};

 

Test* t = new Test;

shared_ptr<Test> local_sp(t);

p->Do();

 

发生什么事呢,t对象被删除了2次!

t对象给了local_sp管理,然后在m_sp =  shared_ptr<Test>(this)这句里又请了一尊神来管理t。

这就发生了条款1里“二龙治水”错误。

 

条款3:shared_ptr作为被保护的对象的成员时,小心因循环引用造成无法释放资源。

 

对象需要相互协作,对象A需要知道对象B的地址,这样才能给对象B发消息(或调用其方法)。

设计模式中有大量例子,一个对象中有其他对象的指针。现在把原生指针替换为shared_ptr.

 

假设a对象中含有一个shared_ptr<B>指向b对象;假设b对象中含有一个shared_ptr<A>指向a对象

并且a,b对象都是堆中分配的。很轻易就能与他们失去最后联系。

考虑某个shared_ptr<A> local_a;是我们能最后一个看到a对象的共享智能指针,其use_count==2,

因为对象b中持有a的指针。所以当local_a说再见时,local_a只是把a对象的use_count改成1。

同理b对象。然后我们再也看不到a,b的影子了,他们就静静的躺在堆里,成为断线的风筝。

 

解决方案是:Use weak_ptr to "break cycles."(boost文档里写的)或者显示的清理

 

条款4:不要在函数实参里创建shared_ptr

 

function ( shared_ptr<int>(new int), g( ) );  //有缺陷

可能的过程是先new int,然后调g( ),g( )发生异常,shared_ptr<int>没有创建,int内存泄露

 

shared_ptr<int> p(new int());

f(p, g());  //Boost推荐写法

 

条款5:对象内部生成shared_ptr

 

前面说过,不能把this指针直接扔给shared_ptr. 但是没有禁止在对象内部生成自己的shared_ptr

 

//这是Boost的例子改的。

class Y: public boost::enable_shared_from_this<Y>

{

    boost::shared_ptr<Y> GetSelf()

    {

        return shared_from_this();

    }

};

 

原理是这样的。普通的(没有继承enable_shared_from_this)类T的shared_ptr<T> p(new T).

p作为栈对象占8个字节,为了记录(new T)对象的引用计数,p会在堆上分配16个字节以保存

引用计数等“智能信息”。share_ptr没有“嵌入(intrusive)”到T对象,或者说T对象对share_ptr毫不知

 

情。Y对象则不同,Y对象已经被“嵌入”了一些share_ptr相关的信息,目的是为了找到“全局性”的

那16字节的本对象的“智能信息”。

 

原理说完了,就是陷阱

Y y;

boost::shared_ptr<Y> p=  y.GetSelf(); //无知的代码,y根本就不是new出来的

 

Y* y = new Y;

boost::shared_ptr<Y> p=  y->GetSelf(); //似是而非,仍旧程序崩盘。

Boost文档说,在调用shared_from_this()之前,必须存在一个正常途径创建的shared_ptr

 

boost::shared_ptr<Y> spy(new Y)

boost::shared_ptr<Y> p =  spy->GetSelf(); //OK

 

条款6 :处理不是new的对象要小心。

 

int* pi = (int*)malloc(4)

shared_ptr<int> sp( pi ) ; //delete马嘴不对malloc驴头。

 

条款7:多线程对引用计数的影响。

 

如果是轻量级的锁,比如InterLockIncrement等,对程序影响不大

如果是重量级的锁,就要考虑因为share_ptr维护引用计数而造成的上下文切换开销。

1.33版本以后的shared_ptr对引用计数的操作使用的是Lock-Free(类似InterLockIncrement函数族)

的操作,应该效率不错,而且能保证线程安全(库必须保证其安全,程序员都没有干预这些隐藏事物的机会)。

Boost文档说read,write同时对shared_ptr操作时,行为不确定。这是因为shared_ptr本身有两个成员px,pi。

多线程同时对px读写是要出问题的。与一个int的全局变量多线程读写会出问题的原因一样。

 

条款8:对象数组用shared_array

 

int* pint = new int[100];

shared_array<int> p (pint );

 

既然shared_ptr对应着delete;显然需要一个delete[]对应物shared_array

 

条款9:学会用删除器

 

struct Test_Deleter

{   

    void  operator ()( Test* p){   ::free(p);   }

};

Test* t = (Test*)malloc(sizeof(Test));

new (t) Test;

 

shared_ptr<Test> sp( t ,  Test_Deleter() ); //删除器可以改变share_ptr销毁对象行为

 

有了删除器,shared_array无用武之地了。

template<class T>

struct Array_Deleter

{   

    void  operator ()( T*){   delete[] p;   }

};

int* pint = new int[100];

shared_ptr<int> p (pint, Array_Deleter<int>() );

 

条款10:学会用分配器

 

存放引用计数的地方是堆内存,需要16-20字节的开销。

如果大量使用shared_ptr会造成大量内存碎片。

shared_ptr构造函数的第3个参数是分配器,可以解决这个问题。

 

shared_ptr<Test> p( (new Test), Test_Deleter(), Mallocator<Test>() );

注意删除器Test_Deleter是针对Test类的。分配器是针对shared_ptr内部数据的。

 

Mallocator<Test>()是个临时对象(无状态的),符合STL分配器规约。

 

template <typename T> 

class Mallocator { 

    //略。。。。。。

    T * allocate(const size_t n) const {

        return singleton_pool<T,sizeof(T)>::malloc();

    }

    //略。。。。。。

 

Mallocator传入Test,实际分配的类型确是

class boost::detail::sp_counted_impl_pda<class Test *,

                                         struct Test_Deleter,

                                         class Mallocator<class Test> >

这是用typeid(T).name()打印出来的。可能和rebind相关。

 

条款11 weak_ptr在使用前需要检查合法性。

weak_ptr<K> wp;

{

shared_ptr<K>  sp(new K);  //sp.use_count()==1

wp = sp; //wp不会改变引用计数,所以sp.use_count()==1

shared_ptr<K> sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象

}

shared_ptr<K> sp_null = wp.lock(); //sp_null .use_count()==0;

因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了。

得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。

因为wp还仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。

直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法直到自己

所容纳的那个指针资源的当前状态。

 

条款12 不要new shared_ptr<T>

 

本来shared_ptr就是为了管理指针资源的,不要又引入一个需要管理的指针资源shared_ptr<T>*

 

条款13  尽量不要get

 

class B{...};

class D : public B{ ...};  //继承层次关系

 

shared_ptr<B> sp (new D);    //通过隐式转换,储存D的指针。

B* b = sp.get();             //shared_ptr辛辛苦苦隐藏的原生指针就这么被刨出来了。

D* d = dynamic_cast<D*>(b);  //这是使用get的正当理由吗?

 

正确的做法

shared_ptr<B> spb (new D)  ;

shared_ptr<D> spd = shared_dynamic_cast<D>(spb); //变成子类的指针

shared_ptr在竭尽全力表演的像一个原生指针,原生指针能干的事,它也基本上能干。

 

另一个同get相关的错误

shared_ptr<T> sp(new T);

shared_ptr<T> sp2( sp.get() ) ;//又一个“二龙治水”实例,指针会删2次而错误。

 

条款14 不要memcpy shared_ptr

 

shared_ptr<B> sp1 (new B)  ;

shared_ptr<B> sp2;

memcpy(&sp2,&sp1,sizeof(shared_ptr<B>)); //sp2.use_count()==1

很显然,不是通过正常途径(拷贝构造,赋值运算),引用计数是不会正确增长的。

 

条款15 使用BOOST预定义的宏去改变shared_ptr行为。

 

shared_ptr行为由类似BOOST_SP_DISABLE_THREADS这样的宏控制。需要去学习他们到底是干什么的。

大师Andrei Alexandrescu设计了一种基于模板策略设计模式的智能指针,通过几个模板参数去定制化

智能指针的行为。Boost却不以为然,官方解释是:需要统一的接口,这样利于大规模书写。

smart_ptr<T,OwnershipPolicy,ConversionPolicy,CheckingPolicy,StoragePolicy> sp(new T);

上述接口缺点是外形复杂,看上去像个大花脸。优点是客户程序员可以轻易的定制行为。

 

条款17 构造函数里调用shared_from_this抛例外

 

class Holder:public enable_shared_from_this<Holder>{

public:

    Holder() {

        shared_ptr<Holder> sp = shared_from_this();

        int x = sp.use_count();

    }

};

同前面条款5,不符合enable_shared_from_this使用前提。

 

总结:

学习了一天就总结出10多条条款,长期研究一下恐怕就出现条款100了。为什么还要使用shared_ptr呢?

有很多开源库用shared_ptr,而且shared_ptr具有“传染性”(某网友语:像毒品沾上就甩不掉),

抛开它就会有更严重的多龙治水现象。shared_ptr作为原生指针的替代品,能解决一定的内存泄露问题。

实际上初学原生指针时,每个人都遇到过野指针,删两次,忘记删除等问题。学习shared_ptr也会遇到。

shared_ptr的确能改善上述问题,并不能完全解决问题。shared_ptr可能在将来占主流,它最可能号令江湖,

否则一大堆auto_ptr,weak_ptr,原生指针,scoped_ptr共存就把人搞糊涂了。

时间: 2024-11-01 04:47:17

学习笔记:shared_ptr陷阱的相关文章

作为一个新手的Oracle(DBA)学习笔记

Oracle数据库笔记 Jack Chaing 作者QQ595696297 交流群 127591054 祝大家学习进步. 如果大家想看Word版本的可以去下载:Word排版比较清晰一些. http://download.csdn.net/detail/jack__chiang/9810532 此笔记是作者本人去年开始从一个DBA新人的学习笔记,积累至今,希望拿出来给那些对DBA有兴趣的童孩学习,大家一起努力嘛. 此笔记记录了作者工作学习中从零基础的学习的记录,和从中遇见的问题与问题的解决!很高兴

Bash学习笔记

                                                                          第1 页        共28页 Bash shell学习笔记 .........................................................................................................................2 1. 引言................

Akka学习笔记(五):Akka与Java的内存模型

Akka学习笔记(五):Akka与Java的内存模型 Akka简化了编写并发软件的过程,本文主要讨论Akka如何在并发应用中访问共享内存. Java内存模型 Java5之前的JMM是相当混乱的.多线程访问共享内存很有可能会得奇怪的结果,如: 可见性问题,无法及时看到其他线程写入的值 指令乱序,观测到其他线程不可能的行为 从Java 5的JSR 133的实现,很多问题就解决了.JMM是基于一组"happens-before"关联规则,限制了访问内存的行为必须在另一个内存访问行为之前发生.

C++内存管理学习笔记(5)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:wdzxl198@163.com  /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9112123 /************************

C++内存管理学习笔记(4)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:wdzxl198@163.com    /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9094793 /**********************

C++内存管理学习笔记(6)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:wdzxl198@163.com    /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9120635 /**********************

Lua学习笔记之类型与值_Lua

基础介绍 Lua是一种动态类型的语言.在语言中没有类型定义的语法,每个值都带有其自身的类型信息.在Lua中有8种基本类型,分别是: nil(空)类型 boolean(布尔)类型 number(数字)类型 string(字符串)类型 userdata(自定义类型) function(函数)类型 thread(线程)类型 table(表)类型 以上是Lua中的8中基本类型,我们可以使用type函数,判断一个值得类型,type函数返回一个对应类型的字符串描述.例如: local iValue = 10

JetSpeed学习笔记(一)

笔记 JetSpeed学习笔记(一) fuweilin 2005-4-7 前言 参加了公司的portal的兴趣小组,今天对portal进行学习.首先上网看了看一些portal的资料,对portal.portlet.portlet container以及JSR168等概念有个基本的了解.决定进一步实战的方式感受portal,于是学习JetSpeed.     1.  JetSpeed介绍JetSpeed是Apache组织开发的一个采用Java和XML的开放源代码的企业信息门户的实现.门户可以让终端

PHP输入输出流学习笔记

  这篇文章主要介绍了PHP输入输出流学习笔记,PHP输入和输出流是通过php://来访问的,它允许访问 PHP 的输入输出流.标准输入输出和错误描述符,内存中.磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器,需要的朋友可以参考下 PHP输入和输出流是通过php://来访问的,它允许访问 PHP 的输入输出流.标准输入输出和错误描述符, 内存中.磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器. php://stdin, php://stdout 和 php://std

PHP学习笔记 (1) 环境配置与代码调试

一配置PHP环境 1.了解什么是PHP PHP("PHP: Hypertext Preprocessor",超文本预处理器的字母缩写) PHP,是英文超级文本预处理语言Hypertext Preprocessor的缩写.PHP 是一种 HTML 内嵌式的语言,是一种在服务器端执行的嵌入HTML文档的脚本语言,语言的风格有类似于C语言,被广泛的运用 2.PHP的背景和优势 PHP的发展背景 1).1994年由Rasmus Lerdorf创建,开始是一个简单的Perl语言编写的程序,用统计