一、指针与智能指针的区别
1、指针
C 语言规定所有变量在使用前必须先定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是专门用来存放地址的,所以必须将它定义为“指针类型”。
2、智能指针
如果在程序中使用 new 从堆(自由存储区)分配内存,等到不需要时,应使用 delete 将其释放。 C++ 引用了智能指针 auto_ptr,以帮助自动完成这个过程。随后的编程体验(尤其是使用STL)表明,需要有更精致的机制。基于程序员的编程体验和 BOOST 库提供的解决方案,C++11摒弃了 auto_ptr,并新增了三种智能指针:unique_ptr、shared_ptr 和 weak_ptr。所有新增的智能指针都能与 STL 容器和移动语义协同工作。
3、智能指针和普通指针的区别
智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,区别是它负责自动释放所指的对象,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。
二、智能指针
1、智能指针的种类
C++中的智能指针有4种,分别为:shared_ptr、unique_ptr、weak_ptr、auto_ptr,其中 auto_ptr被C++11弃用。
2、使用智能指针的原因
申请的空间(即new出来的空间),在使用结束时,需要 delete 掉,否则会形成内存碎片。在程序运行期间,new 出来的对象,在析构函数中 delete 掉,但是这种方法不能解决所有问题,因为有时候 new 发生在某个全局函数里面,该方法会给程序员造成精神负担。此时,智能指针就派上了用场。使用智能指针可以很大程度上避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。所以,智能指针的作用原理就是在函数结束时自动释放内存空间,避免了手动释放内存空间。
3、智能指针的使用
(1)autoi_ptr(C++98 的方案,C++11已经弃用)
auto_ptr 智能指针采用所有权模式;
auto_ptr<string> p1(new string("I reigned loney as a cloud.")); auto_ptr<string> p2; p2=p1; //auto_ptr不会报错
说明:此时不会报错,p2 剥夺了 p1 的所有权(相当于是两个指针同时指向一块内存),但是当程序运行时访问 p1 将会报错。所以auto_ptr 的缺点是:存在潜在的内存崩溃问题。
(2)unique_ptr(替换 auto_ptr)
unique_ptr 实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对 象。它对于避免资源泄露,例如,以 new 创建对象后因为发生异常而忘记调用 delete 时的情形特别 有用。
采用所有权模式,和上面例子一样。
unique_ptr<string> p3(new string("I reigned loney as a cloud.")); unique_ptr<string> p4; p4=p3; //此时会报错
编译器认为P4=P3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。 另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做(即源 unique_ptr 是临时右值时,源 unique_ptr 在赋值后不会出现被访问,所以编译器不会报错),比如:
unique_ptr<string> pu1(new string ("hello world")); unique_ptr<string> pu2; pu2 = pu1; // #1 not allowed unique_ptr<string> pu3; pu3 = unique_ptr<string>(new string ("You")); // #2 allowed
说明:其中#1留下悬挂的 unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的 unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销 毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。
注意:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++ 有一个标准库函数 std::move(),让你能够将一个 unique_ptr 赋给另一个。例如:
unique_ptr<string> ps1, ps2; ps1 = demo("hello"); ps2 = move(ps1); ps1 = demo("alexia"); cout << *ps2 << *ps1 << endl;
(3)shared_ptr
shared_ptr 实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计 数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除 了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用 release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
shared_ptr 智能指针相关成员函数:
1)use_count:返回引用计数的个数;
2)unique:返回是否是独占所有权( use_count 为 1);
3)swap:交换两个 shared_ptr 对象(即交换所拥有的对象);
4)reset:放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少;
5)get:返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get() 是等价的;
(4)weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr。weak_ptr 只是提供了对管理对象的一个访问手 段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增 加或减少。weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题,如果说两个 shared_ptr 相互 引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引 用,不会增加对象的引用计数,和 shared_ptr 之间可以相互转化,shared_ptr 可以直接赋值给 它,它可以通过调用 lock 函数来获得 shared_ptr。
class B; class A { public: shared_ptr<B> pb_; ~A() { cout<<"A delete\n"; } }; class B { public: shared_ptr<A> pa_; ~B() { cout<<"B delete\n"; } }; void fun() { shared_ptr<B> pb(new B()); shared_ptr<A> pa(new A()); pb->pa_ = pa; pa->pb_ = pb; cout<<pb.use_count()<<endl; cout<<pa.use_count()<<endl; } int main() { fun(); return 0; } 1)shared_ptr<B> pb(new B());函数退出时,B被析构,引用计数减一,但是 B 里面还引用了个 A ,此时计数为一,无 法释放资源,如果把两个 share_ptr 中一 个换成 weak_ptr 资源的引用计数为一, 析构后资源会被释放; 2)shared_ptr<A> pa(new A()); 3)虚析构、纯虚析构解决解决的是父类指 针释放子类对象问题。
说明: 可以看到 fun 函数中 pa ,pb 之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针 pa,pb 析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有 被释放(AB的析构函数没有被调用),如果把其中一个改为 weak_ptr 就可以了,我们把类A里面 的 shared_ptr pb_; 改为 weak_ptr pb; 运行结果如下,这样的话,资源B的引用开始就只有1,当 pb 析构时,B 的计数变为 0,B 得到释放,B 释放的同时也会使A的计数减一,同时 pa析构时使A的 计数减一,那么 A 的计数为 0,A 得到释放。
注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能 这样访问,pa->pb->print(); 英文pb是一个weak_ptr,应该先把它转化为shared_ptr,如: shared_ptr p = pa->pb_.lock(); p->print();