shared_ptr简介以及常见问题

http://blog.csdn.net/stelalala/article/details/19993425

本文中的shared_ptr以vs2010中的std::tr1::shared_ptr作为研究对象。可能和boost中的有些许差异,特此说明。

基本功能

shared_ptr提供了一个管理内存的简单有效的方法。shared_ptr能在以下方面给开发提供便利:

1、 使用shared_ptr能有效的解决忘记释放内存带来的内存泄漏问题。同时通过自定义删除器功能还能广泛的用于任何需要”释放”的资源管理。

2、 利用weak_ptr和shared_ptr搭配使用能解决一部分由于重复释放导致的野指针问题。

基本构造方式

不谈拷贝构造的话,shared_ptr的基本构造方式有4种:

1、 无参数构造。

2、 传入一个指针构造一个shared_ptr。例如shared_ptr<Foo> f(new Foo)。

3、 传入一个指针和一个删除器构造一个shared_ptr。例子见后文。

4、 传入一个指针、一个删除器以及一个allocator构造一个shared_ptr。

当然还有一些其他的,例如从auto_ptr从weak_ptr从null_ptr构造。

另外,类似于void*,shared_ptr<void>可以容纳任何类型的指针。

其他常用方法

l use_count()方法。获取到当前智能指针的引用计数。0表示没有任何地方引用。

l get()方法。获取到raw指针。

l reset()方法。重新设置智能指针指向的对象,引用计数重新设置为1。reset方法的函数原型有4种,基本上和前文提到的4种构造函数一一对应。

l swap()方法。交换两个智能指针的内容。

l operater =()方法。该方法会涉及到引用计数的增加。

关于自定义删除器

智能指针能够自定义删除器是一个很重要的功能,该功能使得能够跨dll传递shared_ptr变为可能(当然前提是多个dll使用的shared_ptr实现要一样)。尤其是当c++11的Lambda表达式出现后这个功能用起来更加方便。

先来看自定义删除器的构造方法:

template<class _Ux,

class _Dx>

shared_ptr(_Ux *_Px, _Dx _Dt)

{ // construct with _Px, deleter

_Resetp(_Px, _Dt);

}

其中构造函数的第二个参数就是删除器。这里要求删除器:

1、 是”可调用”的即可,例如function object、函数指针、Lambda表达式、bind/functor等等均可。

2、 返回值是void,参数是Ux*

3、 从形参看出,删除器以传值的方式传入,所以要求删除器要是可拷贝的,否则会编译出错。

4、 删除器不要抛出异常。

例如:

shared_ptr<Foo> shot1(new Foo(1),[&](Foo* p){p->Release();});

make_shared有何用处

boost或者stl都提供了make_shared这个函数。用来方便的创建shared_ptr。

make_shared的好处有两点:

1、 既然用了shared_ptr不用手动delete指针,那么最好也不要在代码中出现new。make_shared正是在函数内封装了new的操作。

2、 从shared_ptr的数据接口了解到,在构造shared_ptr的时候,会new出一个对象保存指针的相关信息。所以一般来说,shared_ptr<Foo> x(new Foo); 需要为Foo 和ref_count 各分配一次内存。如果使用make_shared来创建的话,make_shared内部会尽量将两次内存分配在连续的位置(这个得看用的什么heap管理)。这里理论上能够更快一些。

说下缺点:

1、 make_shared只能针对new出来的,对于使用工厂创建出来的对象无能为力。

2、 需要定制删除器时,make_shared无能为力。

3、 make_shared目前只支持10个参数

另外,make_shared代码很有意思,为了方便的定义10个参数,宏定义用得鬼斧神工。

如何进行类型cast

如果只能指针声明为基类的指针,指向的实际类型是子类的话,shared_ptr会自动完成。其他的转型一眼就能看明白,无需多言:

tr1::const_pointer_cast

tr1::dynamic_pointer_cast

tr1::static_pointer_cast

使用shared_ptr可能会遇到的问题

生命周期的问题

使用shared_ptr的目的就是管理对象的生命周期。在使用了shared_ptr以后有几个事情会变得和以往不太一样。

首先,用了shared_ptr就表明对象是使用引用计数来管理,那么该对象什么时候真正被从内存中释放掉就不是很明显了。比如说,可能你的代码中持有了一份shared_ptr的拷贝,就会导致某个对象一直存留下来。

shared_ptr多次引用同一数据

发生这样的事情后,最好的下场是:后释放的shared_ptr在析构的时候吐核。

在实际编码中要注意。不要把一个raw指针交给多个shared_ptr管理。发生这样的事情很可能是在遗留代码上使用新特性导致的。

this指针的问题

例如这样的例子:

class Foo

{

public:

Foo* GetThis()

{

return this;

}

}

要把这样的代码改为返回shared_ptr<Foo>,不那么好改。假如直接这样修改会有严重的问题:

shared_ptr<Foo> GetThis()

{

return shared_ptr<Foo>(this);

}

因为shared_ptr<Foo>被使用完后就析构了,引用计数减到0以后就会把this delete掉。照成野指针。

为了解决这个问题,标准库提供了一个方法:让类派生自一个模板类:enable_shared_from_this<T>。然后调用shared_from_this()函数即可。

class Foo: public enable_shared_from_this<Foo>

{

public:

shared_ptr<Foo> GetThis()

{

return shared_from_this();;

}

}

这个方法看上去不那么美观,但是确实解决了一些问题。也带来了另一些问题:shared_from_this()这个函数不能够在构造函数中调用。具体原理下一篇文章剖析shared_ptr实现原理时再讲吧。

多线程的问题

shared_ptr的线程安全的定义在boost的文档中有明确的说明:

l 一个shared_ptr对象可以被多个线程同时read

l 两个shared_ptr对象,指向同一个raw指针,两个个线程分别write这两个shared_ptr对象,是安全的。包括析构。

l 多个线程如果要对同一个shared_ptr对象读写,是线程不安全的

也就是说,唯一需要注意的就是:多个线程中对同一个shared_ptr对象读写时需要加锁。但是即使是加锁也有技巧。比较好的方式是:

thread.lock();

shared_ptr tmpPtr=globalSharedPtr; // globalSharedPtr是多个线程读写的那个

thread.unlock();

后面的操作均针对tmpPtr进行

环形引用的问题

环形引用是指这样的情况:

Class A的一个实例中持有一个shared_ptr<B>,Class B的一个实例中持有shared_ptr<A>。考虑以下代码:

class CParent

{

public:

shared_ptr< CChild > children;

};

class CChild

{

public:

shared_ptr< CParent > parent;

};

int main()

{

{

shared_ptr< CParent > pA(new CParent);

shared_ptr< CChild > pB(new CChild);

pA-> children =pB;

pB-> parent =pA;

}

//到这里pA和pB都未能被释放掉

}

要解决环形引用,没有特别好的办法。在分析代码以后,知道了在某个地方可能有环形引用,那么可以使用weak_ptr来替代shared_ptr。

weak_ptr

weak_ptr本身不具有指针的行为,例如你不能对一个weak_ptr来进行*或者->操作。它通常用来和shared_ptr配合使用。

weak_ptr作为一个”shared_ptr的观察者”能够获知shared_ptr的引用计数,还可以获知一个shared_ptr是否已经被析构了。单冲这一点来说,就一点不weak了。

构造weak_ptr

有两种方法可以构造一个weak_ptr

1、 从shared_ptr构造而来。这种情况不会增加shared_ptr的引用计数。当然会增加另一个计数,这个放到下一篇中讲。

2、 从另一个weak_ptr拷贝。

也就是说weak_ptr不可能脱离shared_ptr而存在。

expired()

返回布尔,当返回true的时候表示,weak_ptr关联的shared_ptr已经被析构了。

int _tmain(int argc, _TCHAR* argv[])

{

shared_ptr<foo> fptr=shared_ptr<foo>(new foo(1,2));

weak_ptr<foo> wptr=fptr;

fptr.reset();

if(wptr.expired())

{

cout<<”wptr has expired”<<endl;

}

system(“pause”);

return 0;

}

lock()

从当前的weak_ptr创建一个新的shared_ptr。如果此时expired()返回true时,创建的shared_ptr中将保存一个null_ptr。

use_count()

返回当前关联的shared_ptr的引用计数是多少。expired()返回true时,该函数返回0。

weak_ptr使用场景

weak_ptr的特性是:weak_ptr不会增加shared_ptr的引用计数,所以weak_ptr通常用来解决shared_ptr无法解决的问题,例如环形引用。weak_ptr常见的使用场景有这么几个:

1、 想管理某些资源,但是又不想增加引用计数,那么就可以保存weak_ptr。

2、 当知道了有环形引用后,可以使用weak_ptr。例如上面的例子可以改为这样:

class CParent

{

public:

shared_ptr< CChild > children;

};

class CChild

{

public:

weak_ptr< CParent > parent;

};

int main()

{

{

shared_ptr< CParent > pA(new CParent);

shared_ptr< CChild > pB(new CChild);

pA-> children =pB;

pB-> parent =pA;

}

}

3、 某些情况下,需要知道某个shared_ptr是否已经释放了。

总结

1、 在遗留代码上如果要引入shared_ptr要谨慎!shared_ptr带来的不确定性可能要比带来的便利性大的多。

2、 使用shared_ptr并不是意味着能偷懒。反而你更需要了解用shared_ptr管理的对象的生命周期应该是什么样子的,是不是有环形引用,是不是有线程安全问题,是不是会在某个地方意外的被某个东西hold住了。

3、 一个对象如何使用shared_ptr管理那么最好全部使用shared_ptr来管理,必要的时候可以使用weak_ptr。千万不要raw ptr和智能指针混用

3、 不要以传递指针的形式传递shared_ptr。

4、 多线程读写同一个shared_ptr的时候,可以先加锁拷贝一份出来,然后解锁即可。

参考

1、 1、《Boost程序库完全开发指南》

2、 当析构函数遇到多线程──C++ 中线程安全的对象回调

http://www.cnblogs.com/Solstice/archive/2010/02/10/dtor_meets_threads.html

3、 为什么多线程读写shared_ptr 要加锁

http://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html

4、 vc stl


————————以上文章转自:shared_ptr简介以及常见问题


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383801.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java学习笔记九】多线程

程序&#xff1a;计算机指令的集合&#xff0c;它以文件的形式存储在磁盘上&#xff0c;是应用程序执行的蓝本。 进程&#xff1a;是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位&#xff0c;因此&#xff0c;它使用系统中的运行资源。而…

【C++11新特性】 C++11智能指针之weak_ptr

http://blog.csdn.net/xiejingfa/article/details/50772571 原创作品&#xff0c;转载请标明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50772571 如题&#xff0c;我们今天要讲的是C11引入的三种智能指针中的最后一个&#xff1a;weak_ptr。在学习weak_ptr之…

【C++学习笔记四】运算符重载

当调用一个重载函数和重载运算符时&#xff0c;编译器通过把您所使用的参数类型和定义中的参数类型相比较&#xff0c;巨鼎选用最合适的定义。&#xff08;重载决策&#xff09; 重载运算符时带有特殊名称的函数&#xff0c;函数名是由关键字operator和其后要重载的运算符符号…

【C++11新特性】 C++11智能指针之unique_ptr

原创作品&#xff0c;转载请标明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50759210 在前面一篇文章中&#xff0c;我们了解了C11中引入的智能指针之一shared_ptr&#xff0c;今天&#xff0c;我们来介绍一下另一种智能指针unique_ptr。 unique_ptr介绍 uni…

C++派生类对象和基类对象赋值

在C中&#xff0c;我们允许 将派生类对象赋给基类对象。&#xff08;不允许将基类对象赋给派生类对象&#xff09; 只会将基类对象成员赋值用基类指针指向派生类对象。&#xff08;不允许用派生类指针指向基类对象&#xff09; 基类指针只能操作基类中的成员基类引用作为派生类…

【C++11新特性】 C++11智能指针之shared_ptr

http://blog.csdn.net/Xiejingfa/article/details/50750037 原创作品&#xff0c;转载请标明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50750037 C中的智能指针首先出现在“准”标准库boost中。随着使用的人越来越多&#xff0c;为了让开发人员更方便、更安…

C++(纯)虚函数重写时访问权限更改问题

我们知道在Java中是自动实现多态的&#xff0c;Java中规定重写的方法的访问权限不能缩小。那么在C中我们实现多态的时候是否可以更改&#xff08;缩小&#xff09;访问权限呢&#xff1f; 经过测试&#xff0c;得到的答案如下&#xff1a;如果用基类指针指向派生类对象实现多态…

C++ — 智能指针的简单实现以及循环引用问题

http://blog.csdn.net/dawn_sf/article/details/70168930 智能指针 ____________________________________________________ 今天我们来看一个高大上的东西&#xff0c;它叫智能指针。 哇这个名字听起来都智能的不得了&#xff0c;其实等你了解它你一定会有一点失望的。。。。因…

C++(静态)(常量)数据进行初始化问题以及静态变量析构

在C11标准以前我们都不可以在类中对数据成员初始化&#xff0c;仅能在构造函数中进行初始化&#xff1a; class A {int a,b; double c; string d;A():a(1),b(2),c(3),d(""){} };在C11标准以后我们可以在类中对非静态成员进行初始化。实际上的机制是在调用构造函数的…

C++this指针的用法

参考博客&#xff1a;https://www.cnblogs.com/zhengfa-af/p/8082959.html 在 访问对象的非静态成员时会隐式传递一个参数&#xff0c;即对象本身的指针&#xff0c;这个指针名为this。 例如&#xff1a; class A {int a1;public:A(){}void GetA(int a){cout<<this-&g…

C++开发者都应该使用的10个C++11特性

http://blog.jobbole.com/44015/ 感谢冯上&#xff08;治不好你我就不是兽医 &#xff09;的热心翻译。如果其他朋友也有不错的原创或译文&#xff0c;可以尝试推荐给伯乐在线。】 在C11新标准中&#xff0c;语言本身和标准库都增加了很多新内容&#xff0c;本文只涉及了一些皮…

C++不能被声明为虚函数

虚函数是为了实现多态&#xff0c;但是显然并不是所有函数都可以声明为虚函数的。 不能被声明为虚函数的函数有两类&#xff1a; 不能被继承的函数不能被重写的函数 因此&#xff0c;这些函数都不能被声明为虚函数 普通函数构造函数 如果构造函数定义为虚函数&#xff0c;则…

类的声明与定义

类的前向声明&#xff1a; class A;在声明之后&#xff0c;定义之前&#xff0c;类A是一个不完全类型&#xff0c;即知道A是一个类&#xff0c;但是不知道包含哪些成员。不完全类型只能以有限方式使用&#xff0c;不能定义该类型的对象&#xff0c;不完全类型只能用于定义指向…

shared_ptr的一些尴尬

http://blog.csdn.net/henan_lujun/article/details/8984543 shared_ptr在boost库中已经有多年了&#xff0c;C11又为其正名&#xff0c;把他引入了STL库&#xff0c;放到了std的下面&#xff0c;可见其颇有用武之地&#xff1b;但是shared_ptr是万能的吗&#xff1f;有没有什…

C++转换构造函数和类型转换函数

参考博客&#xff1a;https://blog.csdn.net/feiyanaffection/article/details/79183340 隐式类型转换 如果不同类型的数据在一起操作的时候编译器会自动进行一个数据类型转换。例如常用的基本数据类型有如下类型转换关系&#xff1a; 转换构造函数 构造函数有且仅有一个参数…

C++总结8——shared_ptr和weak_ptr智能指针

http://blog.csdn.net/wendy_keeping/article/details/75268687 智能指针的提出&#xff1a;智能指针是存储指向动态分配对象指针的类&#xff0c;用于生存期控制。能够确保正确销毁动态分配的内存&#xff0c;防止内存泄露。 1.智能指针的分类&#xff1a; 不带引用计数的智能…

C++析构函数执行顺序

今天发现主程序中有多个对象时析构函数的执行顺序不是对象定义的顺序&#xff0c;而是对象定义顺序反过来。 思考了一下&#xff0c;结合之前继承、成员对象等的析构函数执行的顺序&#xff0c;我觉得析构函数执行的顺序为&#xff1a;构造函数的顺序反过来&#xff0c;可能是…

c++写时拷贝1

http://blog.csdn.net/SuLiJuan66/article/details/48882303 Copy On Write Copy On Write(写时复制)使用了“引用计数”&#xff08;reference counting&#xff09;&#xff0c;会有一个变量用于保存引用的数量。当第一个类构造时&#xff0c;string的构造函数会根据传入的参…

【C++学习笔记五】模板

模板是泛型编程的基础 函数模板 模板定义以关键字template开始&#xff0c;后跟一个模板参数列表。这是一个逗号分隔的一个或多个模板参数的列表。用尖括号包围起来。 模板函数定义的一般形式&#xff1a; template <class type> ret-tye func-name(parameter list) …

【Java学习笔记十】输入输出流

在Java.io包中提供了一系列用于处理输入/输出的流类。从功能上分为两类&#xff1a;输入流和输出流。从六结构上可分为&#xff1a;字节流&#xff08;以字节为处理单位&#xff09;和字符流&#xff08;以字符为处理单位&#xff09;。 字符是由字节组成。在Java中所有字符用…