目录
C++11中引入了智能指针的特性,本文将详细介绍智能指针的使用。
我们来看一段代码:
- void Func()
- {
- int* p1 = new int;
- int* p2 = new int;
- cout << div() << endl;
- delete p1;
- delete p2;
- }
在这段代码中,如果在div中抛异常,很显然会造成内存泄漏。为了避免内存泄漏,我们希望有一种指针可以做到自动管理内存释放。
RALL:RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- // 使用RAII思想设计的SmartPtr类
- template<class T>
- class SmartPtr {
- public:
- SmartPtr(T* ptr = nullptr)
- : _ptr(ptr)
- {}
- ~SmartPtr()
- {
- if(_ptr)
- delete _ptr;
- }
-
- T& operator*() {return *_ptr;}
- T* operator->() {return _ptr;}
- private:
- T* _ptr;
- };
智能指针的原理:
在c++的库中定义了几种不同的智能指针,头文件为memory。在智能指针进行拷贝构造或者赋值时,会出现两个指针指向同一块内存的情况,这样在释放空间时就会出现问题。针对这个问题有三种不同的解决方法,对应c++库中的三种指针,前两种都是不成熟的做法,简单了解即可。
C++98版本的库中就提供了auto_ptr的智能指针。
auto_ptr的实现原理:管理权转移的思想,简单的将原指针设为空,把管理权交给新指针。
- auto_ptr(auto_ptr
& sp) - :_ptr(sp._ptr)
- {
- // 管理权转移
- sp._ptr = nullptr;
- }
但是这样会出现很大的问题,因为有时候指针的权限已经发生了转移,但是使用指针的人并不知道,很可能造成越界访问。
结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr。
unique_ptr的实现原理:简单粗暴的防拷贝。
- unique_ptr(const unique_ptr
& sp) = delete; - unique_ptr
& operator=(const unique_ptr& sp) = delete;
这个指针时是不能拷贝的,所以在很多情况下不能使用。
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr。shared_ptr的原理是通过引用计数的方来实现多个shared_ptr对象之间共享资源。
注意:
简单模拟实现如下:
- template<class T>
- class shared_ptr
- {
- public:
- shared_ptr(T* ptr)
- :_ptr(ptr)
- , _pcount(new int(1))
- , _pmutex(new mutex)
- {}
- ~shared_ptr()
- {
- RealseRef();
- }
-
- shared_ptr(const shared_ptr
& sp) - :_ptr(sp._ptr)
- , _pcount(sp._pcount)
- , _pmutex(sp._pmutex)
- {
- AddRef();
- }
- shared_ptr
& operator= (const shared_ptr&sp) - {
- if (_ptr == sp._ptr)
- {
- return *this;
- }
- //先释放掉现在指向的资源
- RealseRef();
- _ptr = sp._ptr;
- _pmutex = sp._pmutex;
- _pcount = sp._pcount;
- AddRef();
- return *this;
- }
- private:
- void AddRef()
- {
- _pmutex->lock();
- (*_pcount)++;
- _pmutex->unlock();
- }
- void RealseRef()
- {
- _pmutex->lock();
- bool flag = false;
- if (--(*_pcount) == 0)
- {
- delete _ptr;
- delete _pcount;
- flag = true;
- }
- _pmutex->unlock();
- if (flag == true)
- {
- delete _pmutex;
- }
- }
- T* _ptr;
- int* _pcount;
- mutex* _pmutex;
- };
shared_ptr的循环引用:
在某些情况下会使用到循环引用,这时候可能会出现问题。
- struct ListNode
- {
- int _data;
- shared_ptr
_prev; - shared_ptr
_next; - ~ListNode(){ cout << "~ListNode()" << endl; }
- };
-
- int main()
- {
- shared_ptr
node1(new ListNode) ; - shared_ptr
node2(new ListNode) ; - node1->_next = node2;
- node2->_prev = node1;
- return 0;
- }
智能指针指向的空间中也保存有智能指针,且两个智能指针指向的空间中保存的智能指针还指向对方的空间,这称为循环引用。

为了解决这个问题。C++中增加了weak_ptr。在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了。
- struct ListNode
- {
- int _data;
- weak_ptr
_prev; - weak_ptr
_next; - ~ListNode(){ cout << "~ListNode()" << endl; }
- };
weak_ptr只是单纯的赋值,不会使引用计数++,析构后也不负责释放空间。 weak_ptr就好比一个局外人,只起到索引的作用。
删除器:
析构智能指针时需要的操作并不相同,有时候传给智能指针的指针可能会一下new出来了多个值,这时候就需要delete[ ],或者传过去的指针是打开文件的指针,析构时不应该delete而是进行关闭文件操作。shared_ptr设计了一个删除器来解决这个问题。

del是一个可调用对象,通过传入del可以自定义析构智能指针时进行的操作。
- template<class T>
- struct DelArr
- {
- void operator()(const T* ptr)
- {
- cout << "delete[]:"<
- delete[] ptr;
- }
- };
- void test_shared_ptr_deletor()
- {
- std::shared_ptr
spArr(new ListNode[10], DelArr()) ; - std::shared_ptr
spfl(fopen("test.txt", "w"), [](FILE* ptr){ - cout << "fclose:" << ptr << endl;
- fclose(ptr);
- });
- }
总结
本文主要简单介绍了智能指针的使用,希望能给大家带来帮助。江湖路远,来日方长,我们下次见~