简单来说智能指针就是一个类似于指针的玩意,帮我们管理一个对象,不需要我们去手动释放指针所指对象。
智能指针(英语:Smart pointer)是一种抽象的数据类型。在程序设计中,它通常是经由类模板来实现,借由模板来达成泛型,借由类别的析构函数来达成自动释放指针所指向的存储器或对象。
智能指针一般会遵循以下两点:
遵循RAII思想——资源分配即初始化重载*、->运算符,有些针对数组的智能指针也会重载[]RAII是英文Resource Acquisition Is Initialization的缩写,指的是资源分配即初始化。
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
因为对于指针而言,指针访问一个对象内部成员通常是使用解引用*加.或者->来访问,比如指针p指向的一个对象中有一个成员num,那么我们访问使用的是(*p).num或者p->num,但是智能指针本身是个类,所以重载*、->是为了让和指针一样去访问成员,方便使用。
我们重载运算符的时候,在使用过程中运算符会被消耗掉,比如重载*,*p就相当于operator*(),在智能指针中返回的是该智能指针所指对象。 类比,如果我们用->,实际是相当于operator->(),此时这个运算符就被消耗了,返回一个智能指针所指对象的指针,问题是如果我们要接着访问对象内成员的话,应该还需要一个->,也就是说我们在用的时候应这么用p->->member,但这样极其不便于书写和阅读习惯,因此在这里编译器对其处理成只需要一个->就能访问成员,这里是比较特殊的地方。
在C++中由于有异常这种扰乱程序执行流程的东西,尽管我们写了new之后马上跟了delete,但是函数里面new了之后还没执行到delete就抛异常跳出函数,导致对象没有被释放造成内存泄漏问题,防不胜防,或者我压根就忘了释放,这个时候就是智能指针登场的时候了。
智能指针本身是一个类,由于类的对象创建时系统会自动调用构造函数,而当对象生命周期结束时,系统会自动调用析构函数,让对象能够自动释放,这样就无需我们手动去管理,也不会出现因执行流被打断造成的内存泄漏问题。
当有如下类
struct ListNode { shared_ptr<ListNode> _next; shared_ptr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } };如果存在两个指针p1和p2
p1->_next = p2;p2->_prev = p1; 此时存在循环引用问题,析构p1的前提是p2->_prev要析构(因为引用计数为2,光析构一个不行),析构p2的前提是p1->_next要析构,这就很麻烦,到了最后析构的时候,引用计数只减了1,两个对象还是没析构掉,反而出现了内存泄漏。。Boost库提供了一种智能指针叫weak_ptr专门解决这类问题,只需要将上述的结构体定义如下,便能解决,由于weak_ptr不影响shared_ptr的引用计数,所以释放时会正常释放
struct ListNode { weak_ptr<ListNode> _next; weak_ptr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } };单个对象比较简单,不多赘述,看代码即可
struct TestClass { public: TestClass() { cout << "TestClass()" << endl; } ~TestClass() { cout << "~TestClass()" << endl; } private: int num; }; void test() { unique_ptr<TestClass> up(new TestClass); shared_ptr<TestClass> sp1(new TestClass); shared_ptr<TestClass> sp2(sp1); cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; cout << sp1.get() << endl; cout << sp2.get() << endl; }多个对象如果默认不处理,最后只会析构一个对象,甚至可能出错,特殊类型也不能用delete简单处理,比如FILE指针必须用fclose处理,这里就涉及到仿函数处理特殊类型了,自定义一个类,重载(),在创建智能指针时把其对象作为第二个参数传入,处理即按照仿函数处理析构,第二个参数就是给我们自定析构用的,默认用delete,自定可以自定析构方式,如delete[]、free、fclose等,实现自动管理。
template <class T> class DeleteArray { void operator()(T* ptr) { cout << "DeleteArray delete array" << endl; delete[] ptr; } }; template <class T> class DeleteFILE { void operator()(T* ptr) { cout << "DeleteArray delete array" << endl; fclose(ptr); } }; void testArray() { unique_ptr<TestClass[]> up_group(new TestClass[10]); //shared智能指针管理数组,自定义删除模式 shared_ptr<TestClass> sp_group(new TestClass[10], DeleteArray<TestClass>()); //管理特殊类型 shared_ptr<FILE> file(fopen("text.txt", "w"), DeleteFILE<FILE>()); }仿函数就是重载了(),比如类A重载了一个函数,有两个int参数,如void operator()(int x1, int x2);,这个时候我们就可以这么调用这个成员,A(1, 2);,由于其调用方式很像函数的调用,但本质是重载了运算符(),因此称之为仿函数。
使用shared_ptr时会有一个安全问题,由于shared_ptr是可以拷贝的,指向内容可以被多个智能指针访问,如果这些指针在不同的线程内,那么会出现线程安全问题,引用计数也是如此,可能存在短时间内被两个指针访问修改的情况,那么就会出问题。
shared_ptr的线程安全可以分为两层:
引用计数线程安全由指针维护,无需我们多管指针所指向内容需要用户自己去维护,这样就需要我们对临界资源进行控制,linux下使用信号量或者互斥锁,Windows的话。。不清楚防拷贝的实现使用delete关键字,删除拷贝构造和operator=(),表明函数已经被删除,无法调用。
template <class T> class ScopedPtr { public: ScopedPtr(T* ptr = new T) :_ptr(ptr) {} ~ScopedPtr() { delete _ptr; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } ScopedPtr(ScopedPtr<T>& sp) = delete; ScopedPtr<T>& operator=(ScopedPtr<T>& sp) = delete; ScopedPtr<T>& operator=(T* ptr) = delete; protected: T* _ptr; }; void testScopePtr() { ScopedPtr<string> sp; //ScopedPtr<string> sp1(sp);//错误,拷贝构造为私有无法调用 //sp1 = sp;//错误,=运算符重载后为私有,无法调用 *sp = "hello world"; cout << *sp << endl; sp->at(0) = 'H';//调用string类的at cout << *sp << endl; }弱指针用于处理循环引用的问题
template <class T> class WeakPtr { public: WeakPtr() :_ptr(NULL) {} WeakPtr(const SharedPtr<T>& sp) :_ptr(sp._ptr) {} WeakPtr(WeakPtr<T>& wp) :_ptr(wp._ptr) {} WeakPtr<T>& operator=(SharedPtr<T>& sp) { _ptr = sp._ptr; return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } protected: T* _ptr; }; struct ListNode { WeakPtr<ListNode> _next; WeakPtr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } }; // 循环引用 void TestCycle() { SharedPtr<ListNode> n1 = new ListNode; SharedPtr<ListNode> n2 = new ListNode; n1->_next = n2; n2->_prev = n1; }