本文共 8739 字,大约阅读时间需要 29 分钟。
为什么需要智能指针呢?
void remodel(std::string & str) { std::string *ps = new std::string(str); ... str = ps; return; } //上述代码会造成内存泄露。所以需要在return之前加入代码: delete ps;然而却有可能很大程度上忘记 void remodel(std::string & str) { std::string *ps = new std::string(str); ... if(..) throw exception(); str = ps; delete ps; return; }当异常发生时,上面delete不会被执行,同样会内存泄露。这时候我们就需要智能指针了。这时我们会想:当remodel这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将自动从栈内存中删除—因此指针ps占据的内存将被释放,如果ps指向的内存也被自动释放,那该有 多好啊。
我们知道析构函数有这个功能。如果ps有一个析构函数,该析构函数将在ps过期时自动释放它指向的内存。
但ps的问题在于,它只是一个常规指针,不是有析构函数的类对象指针。如果它指向的是对象,则可以在对象过期时,让它的析构函数删除指向的内存。
这正是 auto_ptr、unique_ptr和shared_ptr这几个智能指针背后的设计思想。我简单的总结下就是:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
访问智能指针包含的裸指针则可以用 get() 函数。由于智能指针是一个对象,所以if (my_smart_object)永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if (my_smart_object.get())。智能指针包含了 reset() 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。
智能指针类都有一个explicit构造函数 此时,还有需要注意的地方,我们可以看一下auto_ptr如何定义的:
template显而易见,使用了explicit关键字。 explicit关键字只能用于修饰只有一个参数的类的构造函数,它的作用是表面该构造函数是显示的,而非隐式的。 可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。 因此,请看下面的代码:class auto_ptr { public: explicit auto_ptr(X *p = 0) throw(); ... };
shared_ptr智能指针很多方面都类似常规指针 ,ps是一个智能指针的对象,可以对他执行解除引用操作(*ps)、用它来访问结构成员(ps->puffIndex)、将它赋给指向相同类型的常规指针。切忌把智能指针用于非堆内存pd; double *p_reg = new double; pd = p_reg;//错误,不允许隐式转换 pd = shared_ptr (p_reg);//正确 shared_ptr pshared = p_reg;//错误,不允许隐式转换 shared_ptr pshared(p_reg);//正确
string vacation("hello world");
shared_ptr<string>pvac(&vacation); 当pvac过期时,程序将把delete运算符用于非堆内存,将导致错误。
(1)auto_ptr: 转移资源。原理和操作方法如上所说,但是存在这种情况: int a=10; int *p=&a; auto_ptr p1(p); auto_ptr p2(p1); //此时,p1这个智能指针就无法管理p这块内存内存空间了,因为在auto_ptr的拷贝构造函数中是这样实现的: auto_ptr(auto_ptr& p) :_ptr(p._ptr) { _ptr=NULL; } 这种情况下,原来的智能指针p1就无法再对p操作,并且在最终函数结尾,执行析构函数的时候, 还会对p1进行析构,即使此时p1保存的是一个空指针,但是释放造成的开销也是不必要的。 并且:auto_ptr存在以下几个缺陷: 1>不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放; //正确情况: int i=new int(1); //堆上的空间——动态开辟 auto_ptr<int> ap1(&i); //错误情况: int i=1; //栈上的空间 auot_ptr<int> ap2(&i); 2>不要使用两个auto_ptr指针指向同一个指针,具体原因上面解释过; 3>不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配; 4>不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用。 还有最重要的一点就是,什么情况下也别使用auto_ptr智能指针。 (2)unique_ptr unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。 相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期: 从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。 unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、 通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
#include(3)shared_ptr 设计原理:shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。#include int main() { { std::unique_ptr uptr(new int(10)); //创建时绑定动态对象 //std::unique_ptr uptr2 = uptr; //不能賦值 //std::unique_ptr uptr2(uptr); //不能拷貝 std::unique_ptr uptr2 = std::move(uptr); //轉換所有權 uptr2.release(); //释放所有权 std::unique_ptr uptr3; uptr3.reset(new int (3)); //通过reset方法重新指定 } //超過uptr的作用域,內存釋放 }
shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。 不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr<int> p4 = new int(1);的写法是错误的 拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。 get函数获取原始指针 注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存 注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。循环引用在weak_ptr中介绍。
#include注意:std::shared_ptr<int>p(new int (4)); auto p1=std::make_shared<int>(4); //这里p和p1是等价的,但是make_shared<T>是一个非成员函数,使用的好处是可以一次分配共享对象和智能指针自身的内存, 而显式调用shared_ptr构造函数来构造至少需要两次分配内存,出了产生额外的开销,可能还会导致内存泄漏(比如,如果内存分配失败就无法释放内存引起内存泄漏)。 多线程中:线程安全 关于多线程中使用shared_ptr,有如下几点描述: 1. 同一个shared_ptr被多个线程读,是线程安全的; 2. 同一个shared_ptr被多个线程写,不是 线程安全的; 3. 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。 对于第一点,没有什么说的; 对于第二点,同一个shared_ptr在不同的线程中进行写操作不是线程安全的, 那基于第三点,我们一般会有以下方案来实现线程安全:对于线程中传入的外部shared_ptr对象, 在线程内部进行一次新的构造,例如: sharedptr AObjTmp = outerSharedptrObj; (4)weak_ptr weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为, 没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。 weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源, 它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数, 另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。 weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。#include int main() { { int a = 10; std::shared_ptr ptra = std::make_shared (a); std::shared_ptr ptra2(ptra); //copy std::cout << ptra.use_count() << std::endl; int b = 20; int *pb = &a; //std::shared_ptr ptrb = pb; //error std::shared_ptr ptrb = std::make_shared (b); ptra2 = ptrb; //assign pb = ptrb.get(); //获取原始指针 std::cout << ptra.use_count() << std::endl; std::cout << ptrb.use_count() << std::endl; } }
#include当使用shared_ptr的时候,pwin和films[2]指向同一个对象,而引用计数从1增加到了2.在程序的末尾,pwin首先调用其析构函数,该析构函数将引用计数降低到1,所以不会有问题。 即#include int main() { { std::shared_ptr sh_ptr = std::make_shared (10); std::cout << sh_ptr.use_count() << std::endl; std::weak_ptr wp(sh_ptr); std::cout << wp.use_count() << std::endl; if(!wp.expired()){ std::shared_ptr sh_ptr2 = wp.lock(); //get another shared_ptr *sh_ptr = 100; std::cout << wp.use_count() << std::endl; } } //delete memory } #include #include #include using namespace std; int main() { auto_ptr films[5] = { auto_ptr (new string("Fowl Balls")), auto_ptr (new string("Duck Walks")), auto_ptr (new string("Chicken Runs")), auto_ptr (new string("Turkey Errors")), auto_ptr (new string("Goose Eggs")) }; auto_ptr pwin; pwin = films[2]; // films[2] loses ownership. 将所有权从 films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针 //unique_ptr pwin; //编译错误 //pwin = films[2]; //shared_ptr pwin; //没有问题! //pwin = films[2]; cout << "The nominees for best avian baseballl film are\n"; for(int i = 0; i < 5; ++i) cout << *films[i] << endl; cout << "The winner is " << *pwin << endl; cin.get(); return 0; }
使用auto_ptr 所有权转让 运行将会有错误
使用shared_ptr 指向同一对象,引用计数增加,不会有问题
使用unique_ptr 采用所有权模型,编译就会有错误
C++11中的unique_ptr是auto_ptr的替代品,它与auto_ptr一样拥有唯一拥有权的特性,与auto_ptr不一样的是,unique_ptr是没有复制构造函数的,这就防止了一些“悄悄地”丢失所有权的问题发生,如果需要将所有权进行转移,使用move 因此我们也可以看出unique_ptr优于auto_ptr的一个原因: unique_ptr比auto_ptr更安全 还有一个原因就是: auto_ptr只能与new一起使用,不能与new[]一起使用(share_ptr同样) unique_ptr可以使用new,也可以使用new[]
如下面所示:shared_ptr指向数组 在默认情况下,shared_ptr将调用delete进行内存的释放;当分配内存时使用new[]时,我们需要对应的调用delete[]来释放内存;为了能正确的使用shared_ptr指向一个数组,我们就需要定制一个删除函数,例如:
#include#include using namespace std; class A { public: A() { cout<<"constructor"< arrayObj(new A[5], [](A *p){delete[] p;}); return 0; } //但是unque_ptr可以直接可以使用new,也可以使用new[], 如:unique_ptr arrayObj(new A[5]);
转载地址:http://asown.baihongyu.com/