请看下面的代码片断:html
输出结果为:ios
得出结论:第二段程序中,因为fun()函数中的临时变量被销毁,故第二次输出时,p已经成为悬垂指针。c++
指向曾经存在的对象,但该对象已经再也不存在了,此类指针称为垂悬指针。结果未定义,每每致使程序错误,并且难以检测。程序员
引入智能指针能够防止垂悬指针出现。通常是把指针封装到一个称之为智能指针类中,这个类中另外还封装了一个使用计数器,对指针的复制等操做将致使该计数器的值加1,对指针的delete操做则会减1,值为0时,指针为NULL。编程
哑指针指传统的C/C++指针,它只是一个指向,除此之外它不会有其余任何动做,全部的细节必须程序员来处理,好比指针初始化,释放等等数组
“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。人们通常不会错用NULL指针,由于用if语句很容易判断。可是“野指针”是很危险的,if没法判断一个指针是正常指针仍是“野指针”。有个良好的编程习惯是避免“野指针”的惟一方法。缓存
有3种状况可能会形成野指针安全
1:指针变量没有被初始化。任何指针变量刚被建立时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。因此,指针变量在建立的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。less
2:指针p被free或者delete以后,没有置为NULL,让人误觉得p是个合法的指针。别看free和delete的名字(尤为是delete),它们只是把指针所指的内存给释放掉,但并无把指针自己干掉。一般会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错做用,由于即使p不是NULL指针,它也不指向合法的内存块。例函数
另一个要注意的问题:不要返回指向栈内存的指针或引用,由于栈内存在函数结束时会被释放。
3:指针操做超越了变量的做用范围。这种状况让人防不胜防,示例程序以下:
注意:函数 Test1 在执行语句 p->Func()时,对象 a 已经消失,而 p 是指向 a 的,因此 p 就成了“野指针”。
简单的说,智能指针是为了实现相似于Java中的垃圾回收机制。Java的垃圾回收机制使程序员从繁杂的内存管理任务中完全的解脱出来,在申请使用一块内存区域以后,无需去关注应该什么时候何地释放内存,Java将会自动帮助回收。可是出于效率和其余缘由(可能C++设计者不屑于这种傻瓜氏的编程方式),C++自己并无这样的功能,其繁杂且易出错的内存管理也一直为广大程序员所诟病。
更进一步地说,智能指针的出现是为了知足管理类中指针成员的须要。包含指针成员的类须要特别注意复制控制和赋值操做,缘由是复制指针时只复制指针中的地址,而不会复制指针指向的对象。当类的实例在析构的时候,可能会致使垂悬指针问题。
当类中有指针成员时,通常有两种方式来管理指针成员:一是采用值型的方式管理,每一个类对象都保留一份指针指向的对象的拷贝;另外一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。它是指一种实现,能让指针在离开本身生命周期的时候自动销毁指向的内容(对象等),这每每用一个对象将指针包装起来来实现,例如标准库中的auto_ptr和boost中的智能指针都是智能指针的例子,可是缺点就是没有带引用参数。
智能指针的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。智能指针结合了栈的安全性和堆的灵活性,本质上将就是栈对象内部包装一个堆对象
每次建立类的新对象时,初始化指针并将引用计数置为1;当对象做为另外一对象的副本而建立时,拷贝构造函数拷贝指针并增长与之相应的引用计数;对一个对象进行赋值时,赋值操做符减小左操做数所指对象的引用计数(若是引用计数为减至0,则删除对象),并增长右操做数所指对象的引用计数;调用析构函数时,析构函数减小引用计数(若是引用计数减至0,则删除基础对象)。
实现引用计数有两种经典策略:一是引入辅助类,二是使用句柄类。
例如:
在程序中,类TestPtr对象的任何拷贝、赋值操做都会使多个TestPtr对象共享相同的指针。但在一个对象发生析构时,指针指向的对象将被释放,从而可能引发悬垂指针。
方法1:引用计数来解决,一个新的问题是引用计数放在哪里。显然,不能放在TestPtr类中,由于多个对象共享指针时没法同步更新引用计数。
当但愿每一个TestPtr对象中的指针所指向的内容改变而不影响其它对象的指针所指向的内容时,能够在发生修改时,建立新的对象,并修改相应的引用计数。这种技术的一个实例就是写时拷贝(Copy-On-Write)。
缺点是每一个含有指针的类的实现代码中都要本身控制引用计数,比较繁琐。特别是当有多个这类指针时,维护引用计数比较困难。
方法2:
为了不上面方案中每一个使用指针的类本身去控制引用计数,能够用一个类把指针封装起来。封装好后,这个类对象能够出如今用户类使用指针的任何地方,表现为一个指针的行为。咱们能够像指针同样使用它,而不用担忧普通成员指针所带来的问题,咱们把这样的类叫句柄类。在封装句柄类时,须要申请一个动态分配的引用计数空间,指针与引用计数分开存储。
STL中auto_ptr只是众多可能的智能指针之一,auto_ptr所作的事情,就是动态分配对象以及当对象再也不须要时自动执行清理。
注意事项:
一、auto_ptr不能共享全部权。
二、auto_ptr不能指向数组
三、auto_ptr不能做为容器的成员。
四、不能经过赋值操做来初始化auto_ptr
std::auto_ptr<int> p(new int(42)); //OK
std::auto_ptr<int> p = new int(42); //ERROR
这是由于auto_ptr 的构造函数被定义为了explicit
五、不要把auto_ptr放入容器
Boost中的智能指针
智能指针是存储指向动态分配(堆)对象指针的类。除了可以在适当的时间自动删除指向的对象外,他们的工做机制很像C++的内置指针。智能指针在面对异常的时候格外有用,由于他们可以确保正确的销毁动态分配的对象。他们也能够用于跟踪被多用户共享的动态分配对象。
事实上,智能指针可以作的还有不少事情,例如处理线程安全,提供写时复制,确保协议,而且提供远程交互服务。有可以为这些ESP (Extremely Smart Pointers)建立通常智能指针的方法,可是并无涵盖进来。
智能指针的大部分使用是用于生存期控制,阶段控制。它们使用operator->和operator*来生成原始指针,这样智能指针看上去就像一个普通指针。
这样的一个类来自标准库:std::auto_ptr。它是为解决资源全部权问题设计的,可是缺乏对引用数和数组的支持。而且,std::auto_ptr在被复制的时候会传输全部权。在大多数状况下,你须要更多的和/或者是不一样的功能。这时就须要加入smart_ptr类。
scoped_ptr | <boost/scoped_ptr.hpp> | 简单的单一对象的惟一全部权。不可拷贝。 |
scoped_array | <boost/scoped_array.hpp> | 简单的数组的惟一全部权。不可拷贝。 |
shared_ptr | <boost/shared_ptr.hpp> | 在多个指针间共享的对象全部权。 |
shared_array | <boost/shared_array.hpp> | 在多个指针间共享的数组全部权。 |
weak_ptr | <boost/weak_ptr.hpp> | 一个属于 shared_ptr 的对象的无全部权的观察者。 |
intrusive_ptr | <boost/intrusive_ptr.hpp> | 带有一个侵入式引用计数的对象的共享全部权。 |
1. shared_ptr是Boost库所提供的一个智能指针的实现,shared_ptr就是为了解决auto_ptr在对象全部权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了能够共享全部权的智能指针.
2. shared_ptr比auto_ptr更安全
3. shared_ptr是能够拷贝和赋值的,拷贝行为也是等价的,而且能够被比较,这意味这它可被放入标准库的通常容器(vector,list)和关联容器中(map)。关于shared_ptr的使用其实和auto_ptr差很少,只是实现上有差异,关于shared_ptr的定义就不贴代码了,觉得内开源,能够网上找
一、shared_ptr<T> p(new Y);
要了解更多关于auto_ptr的信息,能够查看more effective c++ 的p158页条款28
要了解shared_ptr 类模板信息,能够查看boost 1.37.0中文文档,并且支持数组的shared_array 类模板
在Boost中的智能指针有:
。scoped_ptr,用于处理单个对象的惟一全部权;与std::auto_ptr不一样的是,scoped_ptr能够被复制。
。scoped_array,与scoped_ptr相似,可是用来处理数组的
。shared_ptr,容许共享对象全部权
。shared_array,容许共享数组全部权
scoped_ptr
scoped_ptr智能指针与std::auto_ptr不一样,由于它是不传递全部权的。事实上它明确禁止任何想要这样作的企图!这在你须要确保指针任什么时候候只有一个拥有者时的任何一种情境下都是很是重要的。若是不去使用scoped_ptr,你可能倾向于使用std::auto_ptr,让咱们先看看下面的代码:
auto_ptr MyOwnString?
(new string("This is mine to keep!"));
auto_ptr NoItsMine?(MyOwnString?);
cout << *MyOwnString << endl; // Boom
这段代码显然将不能编译经过,由于字符串的全部权被传给了NoItsMine。这不是std::auto_ptr的设计缺陷—而是一个特性。尽管如此,当你须要MyOwnString达到上面的代码预期的工做效果的话,你可使用scoped_ptr:
scoped_ptr MyOwnString?
(new string("This is mine to keep for real!"));
// Compiler error - there is no copy constructor.
scoped_ptr TryingToTakeItAnyway?
(MyOwnString?);
scoped_ptr经过从boost::noncopyable继承来完成这个行为(能够查看Boost.utility库)。不可复制类声明复制构造函数并将赋值操做符声明为private类型。
scoped_array
scoped_array与scoped_ptr显然是意义等价的,可是是用来处理数组的。在这一点标准库并无考虑—除非你固然可使用std::vector,在大多数状况下这样作是能够的。
用法和scoped_ptr相似:
typedef tuples::tupleint> ArrayTuple?;
scoped_array MyArray?(new ArrayTuple?[10]);
tuples::get<0>(MyArray?[5]) ="The library Tuples is also part of Boost";
tuple是元素的集合—例如两倍,三倍,和四倍。Tuple的典型用法是从函数返回多个值。Boost Tuple库能够被认为是标准库两倍的扩展,目前它与近10个tuple元素一块儿工做。支持tuple流,比较,赋值,卸包等等。
当scoped_array越界的时候,delete[]将被正确的调用。这就避免了一个常见错误,便是调用错误的操做符delete。
这里有一个你在标准库中找不到的—引用数智能指针。大部分人都应当有过使用智能指针的经历,而且已经有不少关于引用数的文章。最重要的一个细节是引用数是如何被执行的—插入,意思是说你将引用计数的功能添加给类,或者是非插入,意思是说你不这样作。Boost shared_ptr是非插入类型的,这个实现使用一个从堆中分配来的引用计数器。关于提供参数化策略使得对任何状况都极为适合的讨论不少了,可是最终讨论的结果是决定反对聚焦于可用性。但是不要期望讨论的结果可以结束。
shared_ptr完成了你所但愿的工做:他负责在不使用实例时删除由它指向的对象(pointee),而且它能够自由的共享它指向的对象(pointee)。
void PrintIfString?(const any& Any) {
if (const shared_ptr* s =
any_cast >(&Any)) {
cout << **s << endl;
}
}
int main(int argc, char* argv[])
{
std::vector Stuff;
shared_ptr SharedString1?
(new string("Share me. By the way,
Boost.any is another useful Boost
library"));
shared_ptr SharedString2?
(SharedString1?);
shared_ptr SharedInt1?
(new int(42));
shared_ptr SharedInt2?
(SharedInt1?);
Stuff.push_back(SharedString1?);
Stuff.push_back(SharedString2?);
Stuff.push_back(SharedInt1?);
Stuff.push_back(SharedInt2?);
// Print the strings
for_each(Stuff.begin(), Stuff.end(),
PrintIfString?);
Stuff.clear();
// The pointees of the shared_ptr's
// will be released on leaving scope
// shared_ptr的pointee离开这个范围后将被释放
return 0;
}
any库提供了存储全部东西的方法[2]HYPERLINK "file:///C:Documents%20and%20SettingsAdministrator桌面My%20Documents新建 CUJhtml20.04karlsson%22%20l"[4]。在包含类型中须要的是它们是可拷贝构造的(CopyConstructible),析构函数这里绝对不能引起,他们应当是可赋值的。咱们如何存储和传递“全部事物”?无区别类型(读做void*)能够涉及到全部的事物,但这将意味着将类型安全(与知识)抛之脑后。any库提供类型安全。全部知足any需求的类型都可以被赋值,可是解开的时候须要知道解开类型。any_cast是解开由any保存着的值的钥匙,any_cast与dynamic_cast的工做机制是相似的—指针类型的类型转换经过返回一个空指针成功或者失败,所以赋值类型的类型转换抛出一个异常(bad_any_cast)而失败。
shared_array与shared_ptr做用是相同的,只是它是用于处理数组的。
shared_array MyStrings?( new Base[20] );
深刻shared_ptr实现
建立一个简单的智能指针是很是容易的。可是建立一个可以在大多数编译器下经过的智能指针就有些难度了。而建立同时又考虑异常安全就更为困难了。Boost::shared_ptr这些全都作到了,下面即是它如何作到这一切的。(请注意:全部的include,断开编译器处理,以及这个实现的部份内容被省略掉了,但你能够在Boost.smart_ptr当中找到它们)。
首先,类的定义:很显然,智能指针是(几乎老是)模板。
template class shared_ptr {
公共接口是:
explicit shared_ptr(T* p =0) : px(p) {
// fix: prevent leak if new throws
try { pn = new long(1); }
catch (...) { checked_delete(p); throw; }
}
如今看来,在构造函数当中两件事情是容易被忽略的。构造函数是explicit的,就像大多数的构造函数同样能够带有一个参数。另一个值得注意的是引用数的堆分配是由一个try-catch块保护的。若是没有这个,你获得的将是一个有缺陷的智能指针,若是引用数没有可以成功分配,它将不能正常完成它本身的工做。
~shared_ptr() { dispose(); }
析构函数执行另一个重要任务:若是引用数降低到零,它应当可以安全的删除指向的对象(pointee)。析构函数将这个重要任务委托给了另一个方法:dispose。
void dispose() { if (—*pn == 0)
{ checked_delete(px); delete pn; } }
正如你所看到的,引用数(pn)在减小。若是它减小到零,checked_delete在所指对象 (px)上被调用,然后引用数(pn)也被删除了。
那么,checked_delete执行什么功能呢?这个便捷的函数(你能够在Boost.utility中找到)确保指针表明的是一个完整的类型。在你的智能指针类当中有这个么?
这是第一个赋值运算符:
template shared_ptr& operator=
(const shared_ptr& r) {
share(r.px,r.pn);
return *this;
}
这是成员模版,若是不是这样,有两种状况:
1. 若是没有参数化复制构造函数,类型赋值Base = Derived无效。
2. 若是有参数化复制构造函数,类型赋值将生效,但同时建立了一个没必要要的临时smart_ptr。
这再一次的展现给你为何不该当加入你本身的智能指针的一个很是好的缘由—这些都不是很明显的问题。
赋值运算符的实际工做是由share函数完成的:
void share(T* rpx, long* rpn) {
if (pn = rpn) { // Q: why not px = rpx?
// A: fails when both == 0
++*rpn; // done before dispose() in case
// rpn transitively dependent on
// *this (bug reported by Ken Johnson)
dispose();
px = rpx;
pn = rpn;
}
}
须要注意的是自我赋值(更准确地说是自我共享)是经过比较引用数完成的,而不是经过指针。为何这样呢?由于它们二者均可以是零,但不必定是同样的。
template shared_ptr
(const shared_ptr& r) : px(r.px) { // never throws
++*(pn = r.pn);
}
这个版本是一个模版化的拷贝构造和函数。能够看看上面的讨论来了解为何要这样作。
赋值运算符以及赋值构造函数在这里一样也有一个非模版化的版本:
shared_ptr(const shared_ptr& r) :
// never throws
px(r.px) { ++*(pn = r.pn); }
shared_ptr& operator=
(const shared_ptr& r) {
share(r.px,r.pn);
return *this;
}
reset函数就像他的名字那样,从新设置所指对象(pointee)。在将要离开做用域的时候,若是你须要销毁所指对象(pointee)它将很是方便的帮你完成,或者简单的使缓存中的值失效。
void reset(T* p=0) {
// fix: self-assignment safe
if ( px == p ) return;
if (—*pn == 0)
{ checked_delete(px); }
else { // allocate new reference
// counter
// fix: prevent leak if new throws
try { pn = new long; }
catch (...) {
// undo effect of —*pn above to
// meet effects guarantee
++*pn;
checked_delete(p);
throw;
} // catch
} // allocate new reference counter
*pn = 1;
px = p;
} // reset
这里仍然请注意避免潜在的内存泄漏问题和保持异常安全的处理手段。
这样你就有了使得智能指针发挥其“智能”的运算符:
// never throws
T& operator*() const { return *px; }
// never throws
T* operator->() const { return px; }
// never throws
T* get() const { return px; }
这仅仅是一个注释:有的智能指针实现从类型转换运算符到T*的转换。这不是一个好主意,这样作常会使你所以受到伤害。虽然get在这里看上去很不舒服,但它阻止了编译器同你玩游戏。
我记得是Andrei Alexandrescu说的:“若是你的智能指针工做起来和哑指针没什么两样,那它就是哑指针。”简直是太对了。
这里有一些很是好的函数,咱们就拿它们来做为本文的结束吧。
long use_count() const
{ return *pn; } // never throws
bool unique() const
{ return *pn == 1; } // never throws
函数的名字已经说明了它的功能了,对么?
关于Boost.smart_ptr还有不少应当说明的(好比std::swap和std::less的特化,与std::auto_ptr榜定在一块儿确保兼容性以及便捷性的成员,等等),因为篇幅限制不能再继续介绍了。详细内容请参考Boost distribution ()的smart_ptr.hpp。即便没有那些其它的内容,你不认为他的确是一个很是智能的指针么?