------------------------------------------------------------------------------------------------------------------------------
本文转自http://www.cppblog.com/yuanyajie/archive/2006/12/15/16489.html
不可否认,资源泄露(resource leak)曾经是C++程序的一大噩梦.垃圾回收机制(Garbage Collection)一时颇受注目.然而垃圾自动回收机制并不能满足内存管理的即时性和可视性,往往使高傲的程序设计者感到不自在.况且,C++实现没有引入这种机制.在探索中,C++程序员创造了锋利的"Smart Pointer".一定程度上,解决了资源泄露问题.
也许,经常的,你会写这样的代码:(//x拟为class:)
class x{ public: int m_Idata; public: x(int m_PARAMin):m_Idata(m_PARAMin){} void print(){ cout<
是的,这里可能没什么问题.可在复杂、N行、m_PTRclassobj所指对象生命周期要求较长的情况下,你能保证你不会忘记delete m_PTRclassobj吗?生活中,我们往往不应该有太多的口头保证,我们需要做些真正有用的东西.还有一个更敏感的问题:异常.假如在#2方法执行期异常发生,函数执行终止,那么new出的对象就会泄露.于是,你可能会说:那么就捕获异常来保证安全性好了.
你写这样的程式:
void fook() { A* m_PTRx = new x(m_PARAMin); try { m_PTRx->DoSomething(); } catch(..) { delete m_PTRx; throw; } delete m_PTRx; }
哦!天哪!想象一下,你的系统,是否会象专为捕获异常而设计的. 一天,有人给你建议:"用Smart Pointer,那很安全.".你可以这样重写你的程序:
void fook() { auto_ptr
OK!你不太相信.不用delete吗?是的.不用整天提心吊胆的问自己:"我全部delete了吗?",而且比你的delete策略更安全.
然后,还有人告诉你,可以这样用呢:
ok1. auto_ptr
但不可这样用:
no1. char* chrarray = new char[100]; strcpy(chrarray,"I am programming."); auto_ptr
预先提及所有权的问题,以便下面带着疑问剖析代码?
power1. auto_ptr
//在上面的#5中,我要告诉你对象所有权转移了两次.
//什么叫对象所有权呢?
上面的一片正确用法,它们在干些什么?
一片非法,它们犯了什么罪?
一片什么所有权转移,它的内部机智是什么?
哦!一头雾水?下面我们就来剖析其实现机制.
基础知识:
a.智能指针的关键技术:在于构造栈上对象的生命期控制堆上构造的对象的生命期.因为在智能指针的内部,存储着堆对象的指针,而且在构析函数中调用delete行为.大致机构如下:
x* m_PTRx = new x(100);//#1 template
b.所有权转移之说
上面曾有一非法的程式片段如下:
auto_ptr
auto_ptr
m_SMPTR2->print();
//输出:100.
m_SMPTR1->print();
//!! 非法的.
按常理来说,m_SMPTR->print();怎么是非法的呢?
那是因为本来,m_SMPTR1维护指向new x(100)的指针,可是m_SMPTR2 = m_SMPTR1;auto_ptr内部机制使得m_SMPTR1将对象的地址传给m_SMPTR2,而将自己的对象指针置为0.那么自然m_SMPTR->print();失败.这里程序设计者要负明显的职责的.那么auto_ptr为什么采取这样的策略:保证所有权的单一性.亦保证了系统安全性.如果多个有全权的auto_ptr维护一个对象,那么在你消除一个auto_ptr时,将导致多个auto_ptr的潜在危险.
下面我们以SGI-STL的auto_ptr设计为样本(去掉了无关分析的宏),来剖析其原理.
template
//下面这片段用于类型转化,目前没有任何编译器支持具体技术细节不诉.
#ifdef __SGI_STL_USE_AUTO_PTR_CONVERSIONS #27 private: #28 template
OK!就是这样了.
正如上面原理介绍处叙说,
你需要正视两大特性:
1.构造栈对象的生命期控制堆上构造的对象的生命期
2.通过release来保证auto_ptr对对象的独权.
在我们对源码分析的基础上,重点看看:
no系列错误在何处?
no1.
我们看到构析函数template
~auto_ptr() _STL_NOTHROW
{ delete _M_ptr; }
所以它不能维护数组,
维护数组需要操作:delete[] _M_ptr;
no2.
先提部分vector和auto_ptr代码:
a.提auto_ptr代码
auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
b.提vector代码
Part1: void push_back(const _Tp& __x) { if (_M_finish != _M_end_of_storage) { construct(_M_finish, __x); ++_M_finish; } else _M_insert_aux(end(), __x); } Part2: template
从提取的vector代码,Part1可看出,push_back的操作行为.
兵分两路,可是再向下看,你会发现,无一例外,都通过const _Tp& 进行拷贝行为,那么从auto_ptr提出的片段就派上用场了. 可你知道的,auto_ptr总是坚持对对象的独权.那必须修改原来维护的对象,而vector行为要求const _Tp&,这样自然会产生问题.一般编译器是可以发觉这种错误的.
其实,STL所有的容器类都采用const _Tp&策略.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 看了sutter和Josuttis的两篇文章中,都提及: +
+ STL容器不支持auto_ptr原因在于copy的对象只是获得所有权的对象, +
+ 这种对象不符合STL的要求.可是本人总感觉即时不是真正的复制对象, +
+ 但我用vector
+ 所谓的完全对象.而且我用自己写的Smart Pointer配合STL容器工作, +
+ 很正常.那需要注意的仅仅是const问题. +
+ +
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
no3.
这个也是auto_ptr隐含的所有权问题引起的.const auto_ptr不允许修改.
随便提及:const对象不代表对象一点不可以改变.在两种const语义下,都有方法修改对象或对象内部指针维护的对象或其它资源.
no4.
再看auto_ptr的构析函数.delete不可以消除栈上资源.
no5.
依赖传入对象指针的构造函数被声明为explicit,禁止隐式转换.
a.类成员auto_ptr,禁止构造函数以构建"完全对象"
Programme1: struct Structx{ int m_Idata; char m_CHRdata; /* and so on */ }; 出于对象编程的理念, 我们将Structx打造成包裹类: class StructWrapper{ private: Structx* m_STRTxptr; public: StructWrapper():m_STRTxptr(new Structx){} ~StructWrapper(){delete m_SMRTxptr; } public: void Soperator1(){ /* 针对Structx对象的特性操作 */} void Soperator2(){ /* 针对Structx对象的特性操作 */} /* and so on */ }; Programme2: class StructWrapper{ private: auto_ptr
处于对构建于堆中的对象(new Structx)智能维护的需要.我们将programme1改造为programme2:不错,对象是可以智能维护了.对于包裹类(StructWrapper)你是否会有这样的构造或指派操作:
StructWrapper m_SMPTRWrapper2(m_SMPTRWrapper1);
StructWrapper mSMPTRWrapper2 = m_SMPTRWrapper1;
那么请注意:
当你坦然的来一个:M_SMPTRWrapper1->Soperator1();的时候,系统崩溃了.不必惊讶,所有权还是所有权问题.问一下自己:当programme2默认拷贝构造函数作用时,又调用了auto_ptr的默认构造函数,那么auto_ptr所有的默认行为都遵循独权策略.对,就这样.m_SMPTRWrapper1的对象所有权转移给了m_SMPTRWrapper2.M_SMPTRWrapper1->Soperator1();那么操作变成了在NULL上的.哦!系统不崩溃才怪.那么你需要想,programme3那样利用auto_ptr的提领操作符自己的构造"完全对象".
b.利用const关键字,防止不经意的权限转移
从上面的叙述,你可看出,所有权转移到处可以酿成大祸.而对于一般应用来说,独权又是很好的安全性策略.那么我们就用const来修饰auto_ptr,禁止不经意的错误.
当然上面提及:并不代表auto_ptr是不可修改的.处于需要,从两种const语义,你都可实现修改.然,你还希望在函数传入传出auto_ptr那么你可传递auto_ptr的引用,那就万无一失了: void fook(const auto_ptr
在实践中,std::auto_ptr能满足你的需求吗?
Andrei Alexandrescu在一篇文章中,提及:有关Smart Pointer的技术就像巫术.Smart Pointer作为C++垃圾回收机制的核心,它必须足够强大的、具有工业强度和安全性.
但为了可一劳永逸我们还需要披荆斩棘继续探索.
下面在需求层面上,我们思索一下我们的智能指针还需要些什么?
a. std::auto_ptr 能够处理数组吗?我们可以用智能指针来管理其它的资源吗?譬如一个线程句柄、一个文件句柄 and so on !
b. 对于我们的对象真的永远实行独权政策吗?
c. Our 智能指针还需要在继承和虚拟层面上发挥威力 !
d. 往往,需要扩展Our 智能指针的功能成员函数来满足动态的需要 !
e. 也许,你需要的还很多.
---------------------------------------------------------------------------------------------------------------------------------
转自 http://www.360doc.com/content/10/0511/11/1372330_27035365.shtml
正文
智能指针能够使C++的开发简单化,主要是它能够像其它限制性语言(如C#、VB)自动管理内存的释放,而且能够做更多的事情。
1、 什么是智能指针
智能指针是一种像指针的C++对象,但它能够在对象不使用的时候自己销毁掉。
我们知道在C++中的对象不再使用是很难定义的,因此C++中的资源管理是很复杂的。各种智能指针能够操作不同的情况。当然,智能指针能够在任务结束的时候删除对象,除了在程序之外。
许多库都提供了智能指针的操作,但都有自己的优点和缺点。Boost库是一个高质量的开源的C++模板库,很多人都考虑将其加入下一个C++标准库的版本中。
Boost提供了下面几种智能指针:
shared_ptr |
本指针中有一个引用指针记数器,表示类型T的对象是否已经不再使用。shared_ptr 是Boost中提供普通的智能指针,大多数地方都使用shared_ptr。 |
scoped_ptr |
当离开作用域能够自动释放的指针。因为它是不传递所有权的。事实上它明确禁止任何想要这样做的企图!这在你需要确保指针任何时候只有一个拥有者时的任何一种情境下都是非常重要的。 |
intrusive_ptr |
比 shared_ptr 更好的智能指针,但是需要类型 T 提供自己的指针使用引用记数机制。 |
weak_ptr |
一个弱指针,帮助shared_ptr 避免循环引用。 |
shared_array |
和 shared_ptr 类似,用来处理数组的。 |
scoped_array |
和 scoped_ptr 类似,用类处理数组的。 |
下面让我们看一个简单的例子:
2、 首先介绍:boost::scoped_ptr
scoped_ptr 是 Boost 提供的一个简单的智能指针,它能够保证在离开作用域后对象被释放。
例子说明:本例子使用了一个帮助我们理解的类: CSample, 在类的构造函数、赋值函数、析构函数中都加入了打印调试语句。因此在程序执行的每一步都会打印调试信息。在例子的目录里已经包含了程序中需要的Boost库的部分内容,不需要下载其它内容(查看Boost的安装指南)。
下面的例子就是使用scoped_ptr 指针来自动释放对象的:
使用普通指针 |
使用scoped_ptr 指针 |
void Sample1_Plain() { CSample * pSample(new CSample);
if (!pSample->Query() ) // just some function... { delete pSample; return; }
pSample->Use(); delete pSample; } |
#include "boost/smart_ptr.h"
void Sample1_ScopedPtr() { boost::scoped_ptr samplePtr(new CSample);
if (!samplePtr->Query() ) // just some function... return;
samplePtr->Use();
} |
使用普通普通指针的时候,我们必须记住在函数退出的时候要释放在这个函数内创建的对象。当我们使用例外的时候处理指针是特别烦人的事情(容易忘记销毁它)。使用scoped_ptr 指针就能够在函数结束的时候自动销毁它,但对于函数外创建的指针就无能为力了。
优点:对于在复杂的函数种,使用scoped_ptr 指针能够帮助我们处理那些容易忘记释放的对象。也因此在调试模式下如果使用了空指针,就会出现一个断言。
优点 |
自动释放本地对象和成员变量[1],延迟实例化,操作PIMPL和RAII(看下面) |
缺点 |
在STL容器里,多个指针操作一个对象的时候需要注意。 |
性能 |
使用scoped_ptr 指针,会增加一个普通指针。 |
3、 引用指针计数器
引用指针计数器记录有多少个引用指针指向同一个对象,如果最后一个引用指针被销毁的时候,那么就销毁对象本身。
shared_ptr 就是Boost中普通的引用指针计数器,它表示可以有多个指针指向同一个对象,看下面的例子:
void Sample2_Shared() { // (A) 创建Csample类的一个实例和一个引用。 boost::shared_ptr printf("The Sample now has %i references\n", mySample.use_count()); // The Sample now has 1 references // (B) 付第二个指针给它。 boost::shared_ptr printf("The Sample now has %i references\n", mySample.use_count());
// (C) 设置第一个指针为空。 mySample.reset(); printf("The Sample now has %i references\n", mySample2.use_count()); // 一个引用
// 当mySample2离开作用域的时候,对象只有一个引用的时候自动被删除。 } |
在(A)中在堆栈重创建了CSample类的一个实例,并且分配了一个shared_ptr指针。对象mySample入下图所示:
然后我们分配了第二个指针mySample2,现在有两个指针访问同一个数据。
我们重置第一个指针(将mySample设置为空),程序中仍然有一个Csample实例,mySample2有一个引用指针。
只要当最有一个引用指针mySample2退出了它的作用域之外,Csample这个实例才被销毁。
当然,并不仅限于单个Csample这个实例,或者是两个指针,一个函数,下面是用shared_ptr的实例:
· 用作容器中。
· 用在PIMPL的惯用手法 (the pointer-to-implementation idiom )。
· RAII(Resource-Acquisition-Is-Initialization)的惯用手法中。
· 执行分割接口。
注意:如果你没有听说过PIMPL (a.k.a. handle/body) 和 RAII,可以找一个好的C++书,在C++中处于重要的内容,一般C++程序员都应该知道(不过我就是第一次看到这个写法)。智能指针只是一中方便的他们的方法,本文中不讨论他们的内容。
PIMPL:如果必须包容一个可能抛异常的子对象,但仍然不想从你自己的构造函数中抛出异常,考虑使用被叫做Handle Class或Pimpl的方法(“Pimpl”个双关语:pImpl或“pointer to implementation”)
4、 主要特点
boost::shared_ptr 有一些重要的特征必须建立在其它操作之上。
· shared_ptr
当声明或定义一个shared_ptr
· shared_ptr
在这里本质上不需要制定T的类型(如从一个基类继承下来的)
· shared_ptr
如果你的类中自己写了释放方法,也可以使用。具体参照Boost文档。
· 强制转换
如果你定义了一个U*能够强制转换到T*(因为T是U的基类),那么shared_ptr也能够强制转换到shared_ptr
· shared_ptr 是线程安全的
(这种设计的选择超过它的优点,在多线程情况下是非常必要的)
· 已经作为一种惯例,用在很多平台上,被证明和认同的。
5、 例子:在容器中使用shared_ptr
许多容器类,包括STL,都需要拷贝操作(例如,我们插入一个存在的元素到list,vector,或者container。)当拷贝操作是非常销毁资源的时候(这些操作时必须的),典型的操作就是使用容器指针。
std::vector vec.push_back( new CMyLargeClass("bigString") ); |
将内存管理的任务抛给调用者,我们能够使用shared_ptr来实现。
typedef boost::shared_ptr std::vector vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) ); |
当vector被销毁的时候,这个元素自动被销毁了。当然,除非有另一个智能指针引用了它,则还本能被销毁。让我们看Sample3中的使用:
void Sample3_Container() { typedef boost::shared_ptr
// (A) create a container of CSample pointers: std::vector
// (B) add three elements vec.push_back(CSamplePtr(new CSample)); vec.push_back(CSamplePtr(new CSample)); vec.push_back(CSamplePtr(new CSample));
// (C) "keep" a pointer to the second: CSamplePtr anElement = vec[1];
// (D) destroy the vector: vec.clear();
// (E) the second element still exists anElement->Use(); printf("done. cleanup is automatic\n");
// (F) anElement goes out of scope, deleting the last CSample instance } |
6、 使用Boost中的智能指针,什么是正确的使用方法
使用智能指针的一些操作会产生错误(突出的事那些不可用的引用计数器,一些对象太容易释放,或者根本释放不掉)。Boost增强了这种安全性,处理了所有潜在存在的危险,所以我们要遵循以下几条规则使我们的代码更加安全。
下面几条规则是你应该必须遵守的:
规则一:赋值和保存 —— 对于智能指针来说,赋值是立即创建一个实例,并且保存在那里。现在智能指针拥有一个对象,你不能手动释放它,或者取走它,这将帮助你避免意外地释放了一个对象,但你还在引用它,或者结束一个不可用的引用计数器。
规则二:_ptr
· 当创建一个智能指针的时候需要明确写出 __ptr
· 不能将T*赋值给一个智能指针。
· 不能写ptr = NULL,应该使用ptr.reset()。
· 重新找回原始指针,使用ptr.get(),不必释放这个指针,智能指针会去释放、重置、赋值。使用get()仅仅通过函数指针来获取原始指针。
· 不能通过T*指向函数指针来代表一个__ptr
· 这是一种特殊的方法来认定这个智能指针拥有的原始指针。不过在Boost:smart pointer programming techniques 举例说明了许多通用的情况。
规则三:非循环引用 —— 如果有两个对象引用,而他们彼此都通过一个一个引用指针计数器,那么它们不能释放,Boost 提供了weak_ptr来打破这种循环引用(下面介绍)。
规则四:非临时的 share_ptr —— 不能够造一个临时的share_ptr来指向它们的函数,应该命名一个局部变量来实现。(这可以使处理以外更安全,Boost share_ptr best practices 有详细解说)。
7、 循环引用
引用计数器是一种便利的资源管理机制,它有一个基本回收机制。但循环引用不能够自动回收,计算机很难检测到。一个最简单的例子,如下:
struct CDad; struct CChild;
typedef boost::shared_ptr typedef boost::shared_ptr
struct CDad : public CSample { CChildPtr myBoy; };
struct CChild : public CSample { CDadPtr myDad; };
// a "thing" that holds a smart pointer to another "thing":
CDadPtr parent(new CDadPtr); CChildPtr child(new CChildPtr);
// deliberately create a circular reference: parent->myBoy = child; child->myDad = dad;
// resetting one ptr... child.reset(); |
parent 仍然引用CDad对象,它自己本身又引用CChild。整个情况如下图所示:
如果我们调用dad.reset(),那么我们两个对象都会失去联系。但这种正确的离开这个引用,共享的指针看上去没有理由去释放那两个对象,我们不能够再访问那两个对象,但那两个对象的确还存在,这是一种非常严重的内存泄露。如果拥有更多的这种对象,那么将由更多的临界资源不能正常释放。
如果不能解决好共享智能指针的这种操作,这将是一个严重的问题(至少是我们不可接受的)。因此我们需要打破这种循环引用,下面有三种方法:
A、 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
B、 当Dad的生存期超过Child的生存期的时候,Child需要一个普通指针指向Dad。
C、 使用boost::weak_ptr打破这种循环引用。
方法A和B并不是一个完美的解决方案,但是可以在不使用weak_ptr的情况下让我们使用智能指针,让我们看看weak_ptr的详细情况。
8、 使用weak_ptr跳出循环
强引用和弱引用的比较:
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。
boost::weak_ptr
struct CBetterChild : public CSample { weak_ptr
void BringBeer() { shared_ptr if (strongDad) // is the object still alive? strongDad->SetBeer(); // strongDad is released when it goes out of scope. // the object retains the weak pointer } }; |
9、 Intrusive_ptr——轻量级共享智能指针
shared_ptr比普通指针提供了更完善的功能。有一个小小的代价,那就是一个共享指针比普通指针占用更多的空间,每一个对象都有一个共享指针,这个指针有引用计数器以便于释放。但对于大多数实际情况,这些都是可以忽略不计的。
intrusive_ptr 提供了一个折中的解决方案。它提供了一个轻量级的引用计数器,但必须对象本身已经有了一个对象引用计数器。这并不是坏的想法,当你自己的设计的类中实现智能指针相同的工作,那么一定已经定义了一个引用计数器,这样只需要更少的内存,而且可以提高执行性能。
如果你要使用intrusive_ptr 指向类型T,那么你就需要定义两个函数:intrusive_ptr_add_ref 和intrusive_ptr_release。下面是一个简单的例子解释如何在自己的类中实现:
#include "boost/intrusive_ptr.hpp"
// forward declarations class CRefCounted;
namespace boost { void intrusive_ptr_add_ref(CRefCounted * p); void intrusive_ptr_release(CRefCounted * p); };
// My Class class CRefCounted { private: long references; friend void ::boost::intrusive_ptr_add_ref(CRefCounted * p); friend void ::boost::intrusive_ptr_release(CRefCounted * p);
public: CRefCounted() : references(0) {} // initialize references to 0 };
// class specific addref/release implementation // the two function overloads must be in the boost namespace on most compilers: namespace boost { inline void intrusive_ptr_add_ref(CRefCounted * p) { // increment reference count of object *p ++(p->references); }
inline void intrusive_ptr_release(CRefCounted * p) { // decrement reference count, and delete object when reference count reaches 0 if (--(p->references) == 0) delete p; } } // namespace boost |
这是一个最简单的(非线程安全)实现操作。但作为一种通用的操作,如果提供一种基类来完成这种操作或许很有使用价值,也许在其他地方会介绍到。
10、 scoped_array 和 shared_array
scoped_array 和 shared_array和上面讲的基本上相同,只不过他们是指向数组的。就像使用指针操作一样使用operator new[] ,他们都重载了operator new[]。注意他们并不初始化分配长度。
11、 Boost的安装
从www.boost.org上下载最新版本的boost,然后解压缩到你指定的目录里,解压缩后的文件目录如下:
Boost\ boost的源文件和头文件。
Doc\ HTML格式的文档。
Lib\ 库文件(不是必需的)
… 其他文件(“more\”里有其他资料)
添加目录到我们自己的IDE里:
VC6:在菜单Tools/Options,Directories tab, "Show Directories for... Include files",
VC7: 在菜单Tools/Options, Projects/VC++ directories, "Show Directories for... Include files".
Boost的头文件都在boost\子目录里,例如本文档例子中有#include "boost/smart_ptr.hpp"。所以任何人当读到年的源文件的时候就立刻知道你用到了boost中的智能指针。
12、 关于本文档中的例子
本文档中的例子里有一个子目录boost\仅仅包含了本例子中使用到的一些头文件,仅仅是为了你编译这个例子,如果你需要下载完整的boost或者获取更多的资源请到www.boost.org。
13、 VC6中min/max的灾难
当在VC中使用boost库,或者其他库的时候会有一些小的问题。
在Windows的头文件中已经定义了min 和 max宏,所以在STL中的这两个函数就调用不到了,例如在MFC中就是这样,但是在Boost中,都是使用的std::命名空间下的函数,使用Windows的函数不能够接受不同类型的参数在模板中使用,但是许多库都依赖这些。
虽然Boost尽量处理这些问题,但有时候遇到这样的问题的时候就需要在自己的代码中加入像下面的代码在第一个#include前加入#define _NOMINMAX。
#define _NOMINMAX // disable windows.h defining min and max as macros #include "boost/config.hpp" // include boosts compiler-specific "fixes" using std::min; // makle them globally available using std::max; |
这样操作并不是在任何时候都需要,而只有我们碰到使用了就需要加入这段代码。
14、 资源
获取更多的信息,或者有问题可以查找如下资源:
· Boost home page
· Download Boost
· Smart pointer overview
· Boost users mailing list
· Boost中的智能指针(撰文 Bjorn Karlsson 翻译 曾毅)
-----------------------------------------------------------------------------------------------------------------------------------
转自 http://hi.baidu.com/cpuramdisk/item/37d4124a22d9c00d6dc2f0da
头文件: "boost/weak_ptr.hpp"
weak_ptr 是 shared_ptr 的观察员。它不会干扰shared_ptr所共享的所有权。当一个被weak_ptr所观察的 shared_ptr 要释放它的资源时,它会把相关的 weak_ptr的指针设为空。这防止了 weak_ptr 持有悬空的指针。你为什么会需要 weak_ptr? 许多情况下,你需要旁观或使用一个共享资源,但不接受所有权,如为了防止递归的依赖关系,你就要旁观一个共享资源而不能拥有所有权,或者为了避免悬空指针。可以从一个weak_ptr构造一个shared_ptr,从而取得对共享资源的访问权。
以下是 weak_ptr的部分定义,列出并简要介绍了最重要的函数。
成员函数template
这个构造函数从一个shared_ptr创建 weak_ptr ,要求可以从 Y* 隐式转换为 T*. 新的 weak_ptr 被配置为旁观 r所引向的资源。r的引用计数不会有所改变。这意味着r所引向的资源在被删除时不会理睬是否有weak_ptr 引向它。这个构造函数不会抛出异常。
weak_ptr(const weak_ptr& r);这个复制构造函数让新建的 weak_ptr 旁观weak_ptr r(译注:原文为shared_ptr r,有误)所引向的资源。weak_ptr(译注:原文为shared_ptr,有误)的引用计数保持不变。这个构造函数不会抛出异常。
~weak_ptr();weak_ptr 的析构函数,和构造函数一样,它不改变引用计数。如果需要,析构函数会把 *this 与共享资源脱离开。这个析构函数不会抛出异常。
bool expired() const;如果所观察的资源已经"过期",即资源已被释放,则返回 True 。如果保存的指针为非空,expired 返回 false. 这个函数不会抛出异常。
shared_ptr返回一个引向weak_ptr所观察的资源的 shared_ptr ,如果可以的话。如果没有这样指针(即 weak_ptr 引向的是空指针),shared_ptr 也将引向空指针。否则,shared_ptr所引向的资源的引用计数将正常地递增。这个函数不会抛出异常。
我们从一个示范weak_ptr的基本用法的例子开始,尤其要看看它是如何不影响引用计数的。这个例子里也包含了 shared_ptr,因为 weak_ptr 总是需要和 shared_ptr一起使用的。使用 weak_ptr 要包含头文件 "boost/weak_ptr.hpp".
weak_ptr w 被缺省构造,意味着它初始时不旁观任何资源。要检测一个 weak_ptr 是否在旁观一个活的对象,你可以使用函数 expired. 要开始旁观,weak_ptr 必须要被赋值一个 shared_ptr. 本例中,shared_ptr p 被赋值给 weak_ptr w, 这等于说p和 w 的引用计数应该是相同的。然后,再从weak_ptr构造一个shared_ptr,这是一种从weak_ptr那里获得对共享资源的访问权的方法。如果在构造shared_ptr时,weak_ptr 已经过期了,将从shared_ptr的构造函数里抛出一个 boost::bad_weak_ptr类型的异常。再继续,当 shared_ptr p 离开作用域,w 就变成过期的了。当调用它的成员函数 lock 来获得一个shared_ptr时,这是另一种获得对共享资源访问权的方法,将返回一个空的 shared_ptr 。注意,从这个程序的开始到结束,weak_ptr 都没有影响到共享对象的引用计数的值。
与其它智能指针不同的是,weak_ptr 不对它所观察的指针提供重载的 operator* 和 operator->. 原因是对weak_ptr所观察的资源的任何操作都必须是明显的,这样才安全;由于不会影响它们所观察的共享资源的引用计数器,所以真的很容易就会不小心访问到一个无效的指针。这就是为什么你必须要传送 weak_ptr 给 shared_ptr的构造函数,或者通过调用weak_ptr::lock来获得一个 shared_ptr 。这两种方法都会使引用计数增加,这样在 shared_ptr 从 weak_ptr创建以后,它可以保证共享资源的生存,确保在我们要使用它的时候它不会被释放掉。
由于在智能指针中保存的是指针的值而不是它们所指向的指针的值,因此在标准库容器中使用智能指针有一个常见的问题,就是如何在算法中使用智能指针;算法通常需要访问实际对象的值,而不是它们的地址。例如,你如何调用 std::sort 并正确地排序?实际上,这个问题与在容器中保存并操作普通指针是几乎一样的,但事实很容易被忽略(可能是由于我们总是避免在容器中保存裸指针)。当然我们不能直接比较两个智能指针的值,但也很容易解决。只要用一个解引用智能指针的谓词就可以了,所以我们将创建一个可重用的谓词,使得可以在标准库的算法里使用引向智能指针的迭代器,这里我们选用的智能指针是 weak_ptr。
weak_ptr_unary_t 函数对象对要调用的函数以及函数所用的参数类型进行了参数化。把要调用的函数保存在函数对象中使用使得这个函数对象很容易使用,很快我们就能看到这一点。为了使这个谓词兼容于标准库的适配器,weak_ptr_unary_t 要从 std::unary_function派生,后者保证了所有需要的 typedefs 都能提供(这些要求是为了让标准库的适配器可以这些函数对象一起工作)。实际的工作在调用操作符函数中完成,从weak_ptr创建一个 shared_ptr 。必须要确保在函数调用时资源是可用的。然后才可以调用指定的函数(或函数对象),传入本次调用的参数(要解引用以获得真正的资源) 和在对象中保存的值,这个值是在构造weak_ptr_unary_t时给定的。这个简单的函数对象现在可以用于任意可用的算法了。为方便起见,我们还定义了一个助手函数,weak_ptr_unary, 它可以推出参数的类型并返回一个适当的函数对象。我们来看看如何使用它。
[14] 要使得这个类型更通用,还需要更多的设计。
- #include
- #include
- #include
- #include
- #include "boost/shared_ptr.hpp"
- #include "boost/weak_ptr.hpp"
- int main() {
- using std::string;
- using std::vector;
- using boost::shared_ptr;
- using boost::weak_ptr;
- vector
> vec; - shared_ptr
sp1( - new string("An example"));
- shared_ptr
sp2( - new string("of using"));
- shared_ptr
sp3( - new string("smart pointers and predicates"));
- vec.push_back(weak_ptr
(sp1)); - vec.push_back(weak_ptr
(sp2)); - vec.push_back(weak_ptr
(sp3)); - vector
>::iterator - it=std::find_if(vec.begin(),vec.end(),
- weak_ptr_unary(std::equal_to
(),string("of using"))); - if (it!=vec.end()) {
- shared_ptr
sp(*++it); - std::cout << *sp << '\n';
- }
- }
本例中,创建了一个包含weak_ptr的 vector。最有趣的一行代码(是的,它有点长)就是我们为使用find_if算法而创建weak_ptr_unary_t的那行。
通过把另一个函数对象,std::equal_to, 和一个用于匹配的string一起传给助手函数weak_ptr_unary,创建了一个新的函数对象。由于 weak_ptr_unary_t 完全兼容于各种适配器(由于它是从std::unary_function派生而来的),我们可以再从它组合出各种各样的函数对象。例如,我们也可以查找第一个不匹配"of using"的串:
Boost智能指针是专门为了与标准库配合工作而设计的。我们可以创建有用的组件来帮助我们可以更简单地使用这些强大的智能指针。象 weak_ptr_unary 这样的工具并不是经常要用到的;有一个库提供了比weak_ptr_unary更好用的泛型绑定器。弄懂这些智能指针的语义,可以让我们更清楚地使用它们。
两种从weak_ptr创建shared_ptr的惯用法[15] 指Boost.Bind库。
如你所见,如果你有一个旁观某种资源的 weak_ptr ,你最终还是会想要访问这个资源。为此,weak_ptr 必须被转换为 shared_ptr, 因为 weak_ptr 是不允许访问资源的。有两种方法可以从weak_ptr创建shared_ptr:把 weak_ptr 传递给 shared_ptr的构造函数,或者调用 weak_ptr 的成员函数lock, 它返回 shared_ptr. 选择哪一个取决于你认为一个空的 weak_ptr 是错误的抑或不是。shared_ptr 构造函数在接受一个空的 weak_ptr 参数时会抛出一个 bad_weak_ptr 类型的异常。因此应该在你认为空的 weak_ptr 是一种错误时使用它。如果使用 weak_ptr 的函数 lock, 它会在weak_ptr为空时返回一个空的 shared_ptr。这在你想测试一个资源是否有效时是正确的,一个空的 weak_ptr 是预期中的。此外,如果使用 lock, 那么使用资源的正确方法应该是初始化并同时测试它,如下:
如你所见, p 被weak_ptr wp 的lock函数的结果初始化。然后 p 被测试,只有当它非空时资源才能被访问。由于 shared_ptr 仅在这个作用域中有效,所以在这个作用域之外不会有机会让你不小心用到它。另一种情形是当 weak_ptr 逻辑上必须非空的时候。那种情形下,不需要测试 shared_ptr 是否为空,因为 shared_ptr 的构造函数会在接受一个空weak_ptr时抛出异常,如下:
在这个例子中,函数 access_the_resource 从一个weak_ptr构造 shared_ptr sp 。这时不需要测试 shared_ptr 是否为空,因为如果 weak_ptr 为空,将会抛出一个 bad_weak_ptr 类型的异常,因此函数会立即结束;错误会在适当的时候被捕获和处理。这样做比显式地测试 shared_ptr 是否为空然后返回要更好。这就是从weak_ptr获得shared_ptr的两种方法。
weak_ptr 是Boost智能指针拼图的最后一块。weak_ptr 概念是shared_ptr的一个重要伙伴。它允许我们打破递归的依赖关系。它还处理了关于悬空指针的一个常见问题。在共享一个资源时,它常用于那些不参与生存期管理的资源用户。这种情况不能使用裸指针,因为在最后一个 shared_ptr 被销毁时,它会释放掉共享的资源。如果使用裸指针来引用资源,将无法知道资源是否仍然存在。如果资源已经不存在,访问它将会引起灾难。通过使用 weak_ptr, 关于共享资源已被销毁的信息会传播给所有旁观的 weak_ptrs,这意味着不会发生无意间访问到无效指针的情形。这就象是观察员模式(Observer pattern)的一个特例;当资源被销毁,所有表示对此感兴趣的都会被通知到。
对于以下情形使用 weak_ptr :
1. 要打破递归的依赖关系
2. 使用一个共享的资源而不需要共享所有权
3. 避免悬空的指针
-----------------------------------------------------------------------------------------------------------------------------------
本文转自http://ertou.blog.51cto.com/789994/224800
// 示例 1(b): 安全代码, 使用了auto_ptr
void f()
{
auto_ptr pt( new T );
.....
} // 酷: 当pt出了作用域时析构函数被调用,从而对象被自动删除
// 示例 2: 使用一个 auto_ptr
void g()
{
T* pt1 = new T; // 现在,我们有了一个分配好的对象
auto_ptr auto_pt2( pt1 ); // 将所有权传给了一个auto_ptr对象,auto_pt2 指向了 pt1
// 使用auto_ptr就像我们以前使用简单指针一样
*auto_pt2 = 12; // 就像 "*pt1 = 12;"
auto_pt2->SomeFunc(); // 就像 "pt1->SomeFunc();"
// 用get()来获得指针的值
assert( pt1 == auto_pt2.get() ); // 二者一样
// 用release()来撤销所有权, auto_pt2 把保存的指针地址给了pt3, 而自己指向了NUll。
T* pt3 = auto_pt2.release(); //
// 自己删除这个对象,因为现在没有任何auto_ptr拥有这个对象
delete pt3;
} // pt2不再拥有任何指针,所以不要试图删除它...ok,不要重复删除
// 示例 3: 使用reset()
//
void h()
{
auto_ptr pt( new T(1) );
pt.reset( new T(2) ); //即pt会首先delete pt目前指向的地址(new T(1)得到的地址),
//然后再指向new T(2)分配的地址
} // 最后,pt出了作用域,
// 第二个T也被自动删除了
#include
#include
#include
class implementation
{
public:
~implementation() { std::cout <<"destroying implementation/n"; }
void do_something() { std::cout << "did something/n"; }
};
void test()
{
boost::shared_ptr sp1(new implementation());
std::cout<<"The Sample now has "< sp2 = sp1;
std::cout<<"The Sample now has "<
void test()
{
foo(boost::shared_ptr(new implementation()),g());
}
正确的用法为:
void test()
{
boost::shared_ptr sp (new implementation());
foo(sp,g());
}
-----------------------------------------------------------------------------------------------------------------------------------
转自 http://soft-app.iteye.com/blog/921974
场景:
一个多线程的C++程序,24h x 5.5d运行。有几个工作线程ThreadW{0,1,2,3},处理客户发过来的交易请求,另外有一个背景线程ThreadB,不定期更新程序内部的参考数据。这些线程都跟一个hash表打交道,工作线程只读,背景线程读写,必然要用到一些同步机制,防止数据损坏。这里的示例代码用std::map代替hash表,意思是一样的:
------------------------------------------------------------------------------------------------------------------------------
本文转自http://ertou.blog.51cto.com/789994/224818
2001 年10 月和2002 年4 月,在美国的华盛顿和荷兰的安的列斯群岛上分别召开了两次C++标准会议。会议的内容之一是对一项新的C++特性提议——智能指针(Smart Pointer)——进行讨论。本文将对可能成为C++新标准的两种智能指针方案(Boost vs. Loki)进行介绍和分析,并给出了相应的使用实例。
关键词:智能指针 C++ Boost Loki
在现在的标准C++中,只有一种智能指针:std::auto_ptr。其原因并非是因为auto_ptr 已足以应付所有相关的工作——实际上,auto_ptr 有一个重大的缺陷,就是它不能被用在STL 容器中——而是因为现在的C++标准在制定时并未能对智能指针进行全面的考察。按照C++标准委员会成员Herb Sutter 的说法,只有一种标准的智能指针是一件“可羞”的事情:首先,智能指针所能做的许多有用的事情,是可怜的auto_ptr 不能完成的;其次,在有些情况下使用auto_ptr 可能会造成问题,上面所说的不能在容器中使用就是一例。实际上,许多程序员已经开发了各种有用的智能指针,有些甚至在auto_ptr 被定为标准之前就已存在,但问题是,它们不是标准的。在这样的情况下,C++标准委员会考虑引入新的智能指针,也就是自然而然的事情了。目前进入委员会视野的,主要有两种智能指针方案:Boost 智能指针和Loki 智能指针。前者是由C++标准委员会库工作组发起的Boost 组织开发的,而后者由世界级的C++专家Andrei Alexandrescu 开发,并在他所著的“Modern C++ Design”一书中进行了详细的阐释。下面,让我们分别来看一看这两种方案各自的技术特点。
一、 Boost 智能指针
Boost 的智能指针方案实现了五种智能指针模板类,每种智能指针都用于不同的目的。这五种智能指针是:
template class scoped_ptr;
template class scoped_array;
template class shared_ptr;
template class shared_array;
template class weak_ptr;
class CTest
{
public:
CTest() : m_id(0) {}
CTest(int id) : m_id(id) {}
~CTest() { std::cout << "id: " << m_id << " - Destructor is being called/n"; }
void SetId(int id) { m_id = id; }
int GetId() { return m_id; }
void DoSomething()
{ std::cout << "id: " << m_id << " - Doing something/n"; }
private:
int m_id;
};
void main()
{
boost::scoped_ptr pTest(new CTest);
pTest->DoSomething();
}
void main()
{
boost::scoped_array pTest(new CTest[2]);
pTest[0].SetId(0);
pTest[1].SetId(1);
pTest[0].DoSomething();
pTest[1].DoSomething();
std::cout << '/n';
}
其运行结果为:
id: 0 - Doing something
id: 1 - Doing something
id: 1 - Destructor is being called
id: 0 - Destructor is being called
scoped_array 将负责使用delete [],而不是delete 来删除它所指向的对象。
shared_ptr:意在用于对被指向对象的所有权进行共享。与scoped_ptr 一样,被指向对象也保证会被删除,但不同的是,这将发生在最后一个指向它的shared_ptr 被销毁时,或是调用reset 方法时。shared_ptr符合C++标准库的“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求,所以可被用于标
准的库容器中。另外它还提供了比较操作符,所以可与标准库的关联容器一起工作。shared_ptr 不能用于存储指向动态分配的数组的指针,这样的情况应该使用shared_array。该模板的实现采用了引用计数技术,所以无法正确处理循环引用的情况。可以使用weak_ptr 来“打破循环”。shared_ptr 还可在多线程环境中使用。
下面的例子演示怎样将shared_ptr 用于std::vector 中:
typedef boost::shared_ptr TestPtr;
void PT(const TestPtr &t)
{
std::cout << "id: " << t->GetId() << "/t/t" << "use count: " << t.use_count() << '/n';
}
void main()
{
std::vector TestVector;
TestPtr pTest0(new CTest(0));
TestVector.push_back(pTest0);
TestPtr pTest1(new CTest(1));
TestVector.push_back(pTest1);
TestPtr pTest2(new CTest(2));
TestVector.push_back(pTest2);
std::for_each(TestVector.begin(), TestVector.end(), PT);
std::cout << '/n';
pTest0.reset();
pTest1.reset();
pTest2.reset();
std::for_each(TestVector.begin(), TestVector.end(), PT);
std::cout << '/n';
TestVector.clear();
std::cout << '/n';
std::cout << "exiting.../n";
}
void main()
{
boost::shared_array pTest1(new CTest[2]);
pTest1[0].SetId(0);
pTest1[1].SetId(1);
std::cout << "use count: " << pTest1.use_count() << "/n/n";
boost::shared_array pTest2(pTest1);
std::cout << "use count: " << pTest1.use_count() << "/n/n";
pTest1.reset();
pTest2[0].DoSomething();
pTest2[1].DoSomething();
std::cout << '/n';
std::cout << "use count: " << pTest1.use_count() << "/n/n";
}
void main()
{
boost::shared_ptr pTest(new CTest);
boost::weak_ptr pTest2(pTest);
if(boost::shared_ptr pTest3 = boost::make_shared(pTest2))
pTest3->DoSomething();
pTest.reset();
assert(pTest2.get() == NULL);
}
template
<
typename T,
template class OwnershipPolicy =RefCounted,
class ConversionPolicy =DisallowConversion,
template class CheckingPolicy =AssertCheck,
template class StoragePolicy =DefaultSPStorage
>
class SmartPtr;
shared_ptr:
template // typedef 模板还不是标准的
typedef Loki::SmartPtr
<
T,
RefCounted, // 以下都是缺省的模板参数
DisallowConversion,
AssertCheck,
DefaultSPStorage
>
shared_ptr;
typedef Loki::SmartPtr TestPtr;
void PT(const TestPtr &t)
{
std::cout << "id: " << t->GetId() << '/n';
}
void main()
{
std::vector TestVector;
TestPtr pTest0(new CTest(0));
TestVector.push_back(pTest0);
TestPtr pTest1(new CTest(1));
TestVector.push_back(pTest1);
std::for_each(TestVector.begin(), TestVector.end(), PT);
std::cout << '/n';
Loki::Reset(pTest0, NULL);
Loki::Reset(pTest1, NULL);
std::for_each(TestVector.begin(), TestVector.end(), PT);
std::cout << '/n';
TestVector.clear();
std::cout << '/n';
std::cout << "exiting.../n";
}
namespace Loki
{
// 实现TSS 的Loki 存储策略。改编自Douglas C. Schmidt、Timothy H. Harrison
// 和Nat Pryce 的论文“Thread-Specific Storage for C/C++”中的部分代码。
// def T& ReferenceType; // operator*所返回的类型。
public:
// 构造器。对成员变量进行初始化。
TS_SPStorage() : once_(0), key_(0), keylock_(NULL)
{ pthread_mutex_init(&keylock_, NULL); }
// 析构器。释放先前获取的资源。
~TS_SPStorage()
{
pthread_mutex_destroy(&keylock_);
pthread_key_delete(key_);
}
// 返回线程专有的被指向对象。
PointerType operator->() const { return GetPointee(); }
// 返回线程专有的被指向对象的引用。
ReferenceType operator*() { return *GetPointee(); }
// Accessors。获取线程专有的被指向对象。
friend inline PointerType GetImpl(TS_SPStorage& sp)
{ return sp.GetPointee(); }
// 获取线程专有的被指向对象的引用。
// 该函数没有实现。但NoCheck 需要它才能正常工作。
friend inline const StoredType& GetImplRef(const TS_SPStorage& sp)
{ return 0; }
protected:
// 销毁所存储的数据。空函数。该工作将由CleanupHook()在各个线程退出时完成。
void Destroy() {}
// 获取当前线程专有的数据。
PointerType GetPointee()
{
PointerType tss_data = NULL;
// 使用双重检查锁定模式来在除了初始化以外的情况下避免锁定。
// 之所以在这里,而不是在构造器中对“专有钥”进行初始化及分配TS 对象,
// 是因为:(1) 最初创建TS 对象的线程(例如,主线程)常常并不是使用
// 它的线程(工作线程),所以在构造器中分配一个TS 对象的实例常常并无
// 好处,因为这个实例只能在主线程中访问。(2)在有些平台上,“专有
// 钥”是有限的资源,所以等到对TS 对象进行第一次访问时再进行分配,有
// 助于节省资源。
// 第一次检查。
if(once_ == 0)
{
// 加锁。确保访问的序列化。
pthread_mutex_lock(&keylock_);
// 双重检查。
if(once_ == 0)
{
// 创建“专有钥”。
pthread_key_create(&key_, &CleanupHook);
// 必须在创建过程的最后出现,这样才能防止其他线程在“专有钥”
// 被创建之前使用它。
once_ = 1;
}
// 解锁。
pthread_mutex_unlock(&keylock_);
}
// 从系统的线程专有存储中获取数据。注意这里不需要加锁。
tss_data = (PointerType)pthread_getspecific(key_);
// 检查是否这是当前线程第一次进行访问。
if (tss_data == NULL)
{
// 从堆中为TS 对象分配内存。
tss_data = new T;
// 将其指针存储在系统的线程专有存储中。
pthread_setspecific(key_, (void *)tss_data);
}
return tss_data;
}
private:
// 用于线程专有数据的“专有钥”。
pthread_key_t key_;
// “第一次进入”标志。
int once_;
// 用于避免在初始化过程中产生竞态情况。
pthread_mutex_t keylock_;
// 清扫挂钩函数,释放为TS 对象分配的内存。在每个线程退出时被调用。
static void CleanupHook (void *ptr)
{
// 这里必须进行类型转换,相应的析构器才会被调用(如果有的话)
delete (PointerType)ptr;
}
};
// 用于模拟typedef template 的结构。
// 参见Herb Sutter 的“Template Typedef”一文。
struct TS_SPStorageWrapper
{
template
struct In
{
typedef TS_SPStorage type;
};
};
使用了“Loki VC 6.0 Port”。
template class TS_SPStorage
{
public:
typedef T* StoredType; // 被指向对象的类型。
typedef T* PointerType; // operator->所返回的类型。
}
下面让我们来看一个使用实例。首先让我们先定义:
Loki::SmartPtr
<
int,
Loki::NoCopyWrapper,
Loki::DisallowConversion,
Loki::NoCheckWrapper,
Loki::TS_SPStorageWrapper
> value;
pthread_mutex_t io_mutex = NULL; // iostreams 不一定是线程安全的!
void *thread_proc(void *param)
{
int id = (int)param;
*value = 0;
for (int i = 0; i < 3; i++)
{
(*value)++;
pthread_mutex_lock(&io_mutex);
std::cout << "thread " << id << ": " << *value << '/n';
pthread_mutex_unlock(&io_mutex);
}
return NULL;
}
void main(int argc, char* argv[])
{
pthread_mutex_init(&io_mutex, NULL);
pthread_t id[3];
for(int i = 0; i < 3; i++)
pthread_create(&id[i], 0, thread_proc, (void *)i);
for(int i = 0; i < 3; i++)
pthread_join(id[i], NULL);
pthread_mutex_destroy(&io_mutex);
}