关于C++的智能指针
一句话概括:当类中有指针成员时,可以使用智能指针实现对象共享;智能指针通过引用计数实现,即对指向同一对象的指针计数;智能指针的使用可以方便/安全地控制对象的生命周期,对指针进行自动销毁。
当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。
智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。
在C++中,我们知道,如果使用普通指针来创建一个指向某个对象的指针,那么在使用完这个对象之后我们需要自己删除它,例如:
ObjectType* temp_ptr = new ObjectType();
temp_ptr->foo();
delete temp_ptr;
①如果忘记在调用完之后删除temp_ptr,那么会造成一个悬挂指针(dangling pointer),也就是说这个指针现在指向的内存区域其内容程序员无法把握和控制,也可能非常容易造成内存泄漏。
②可是事实上,不止是“忘记”,在上述的这一段程序中,如果foo()在运行时抛出异常,那么temp_ptr所指向的对象仍然不会被安全删除。
在这个时候,智能指针的出现实际上就是为了可以方便的控制对象的生命期,在智能指针中,一个对象什么时候和在什么条件下要被析构或者是删除是受智能指针本身决定的,用户并不需要管理。
有四种类型的智能指针:引用自https://www.zhihu.com/question/20368881/answer/14918675
1) scoped_ptr: 这是比较简单的一种智能指针,正如其名字所述,scoped_ptr所指向的对象在作用域之外会自动得到析构,一个例子是:https://www.boost.org/doc/libs/1_50_0/libs/smart_ptr/scoped_ptr.htm
此外,scoped_ptr是non-copyable的,也就是说你不能去尝试复制一个scoped_ptr的内容到另外一个scoped_ptr中,这也是为了防止错误的多次析构同一个指针所指向的对象。
2) shared_ptr: 很多人理解的智能指针其实是shared_ptr这个范畴。shared_ptr中实现的本质是引用计数(reference counting),也就是说shared_ptr是支持复制的,复制一个shared_ptr的本质是对这个智能指针的引用次数加1,而当这个智能指针的引用次数降低到0的时候,该对象自动被析构。需要特别指出的是,如果shared_ptr所表征的引用关系中出现一个环,那么环上所述对象的引用次数都肯定不可能减为0那么也就不会被删除,为了解决这个问题引入了weak_ptr。
3) weak_ptr: 对weak_ptr起的作用,很多人有自己不同的理解,我理解的weak_ptr和shared_ptr的最大区别在于weak_ptr在指向一个对象的时候不会增加其引用计数,因此你可以用weak_ptr去指向一个对象并且在weak_ptr仍然指向这个对象的时候析构它,此时你再访问weak_ptr的时候,weak_ptr其实返回的会是一个空的shared_ptr。实际上,通常shared_ptr内部实现的时候维护的就不是一个引用计数,而是两个引用计数,一个表示strong reference,也就是用shared_ptr进行复制的时候进行的计数,一个是weak reference,也就是用weak_ptr进行复制的时候的计数。weak_ptr本身并不会增加strong reference的值,而strong reference降低到0,对象被自动析构。为什么要采取weak_ptr来解决刚才所述的环状引用的问题呢?需要注意的是环状引用的本质矛盾是不能通过任何程序设计语言的方式来打破的,为了解决环状引用,第一步首先得打破环,也就是得告诉C++,这个环上哪一个引用是最弱的,是可以被打破的,因此在一个环上只要把原来的某一个shared_ptr改成weak_ptr,实质上这个环就可以被打破了,原有的环状引用带来的无法析构的问题也就随之得到了解决。
4) intrusive_ptr: 简单的说,intrusive_ptr和shared_ptr的区别在于intrusive_ptr要求其所指向的对象本身实现一个引用计数机制,也就是说当对象本身包含一个reference counter的时候,可以使用intrusive_ptr。在实际使用中我几乎从来没有见到过intrusive_ptr...
传统指针的问题:
当你在堆上创建了一个对象时,系统就把这个对象的生命期完全交给了你,当用完之后,系统并不会回收资源,而是需要你来释放它。
那么,既然要负责对象的释放问题,就要知道什么时候释放和在哪里释放。如果你没有处理好这两个问题,就会造成内存泄漏或程序崩溃的问题。
//1 内存泄漏 str1所指的资源没有被释放 { string* str1 = new string("hello"); string* str2 = new string("world"); } //2 多重释放,引起程序崩溃 { string* str1 = new string("hello"); delete str1; //... delete str1;//引起程序崩溃 }
在上述的例子中,指针的释放是在同一作用域中进行的,大家在编程中也能很容易避免上述代码中的问题。
但是,对于一个大型项目,在某一处创建的对象,可能并不会在对应作用域中释放,而是等到某些事件发生,异常处理等情况下才会去销毁对象,对于这样的问题往往是很难排查出来的。
所以,有必要引用一种机制来负责指针的自动销毁。而不是由程序员本身去手动销毁。智能指针恰恰就是这样的一种机制。
引用计数的智能指针原理:(来自:知乎:小豆君的干货铺)
1. 当从堆上申请了一个资源时,我们就创建一个智能指针对象,使它指向这个资源,同时,在堆上申请一个用于计数的资源,让后来所有的指向该资源的对象都共享这个计数资源,这样,引用计数的个数就只有一份。
2. 当将ptr1对象赋值给对象ptr2时,其共享的引用计数变为2。
3. 删除ptr2对象时,其对应的引用计数减为1。
4. 删除ptr1对象时,引用计数变为0,则释放资源。
下面这段代码来自:知乎:小豆君的干货铺
#pragma once template<class T> class SharedPointer { public: //默认构造函数,内部指针,未指向任何资源,引用计数为0,因为它未与任何资源绑定 SharedPointer() :m_refCount(nullptr), m_pointer(nullptr){} //构造函数,初始化时,指向一个已经分配好的资源 SharedPointer(T* adoptTarget) :m_refCount(nullptr), m_pointer(adoptTarget) { addReference(); } //构造函数,使用其它对象创建新对象 SharedPointer(const SharedPointer<T>& copy) :m_refCount(copy.m_refCount), m_pointer(copy.m_pointer) { addReference(); } //析构函数,引用计数递减,当为0时,释放资源 virtual ~SharedPointer() { removeReference(); } //赋值操作 //当左值被赋值时,表明它不再指向所指的资源,故引用计数减一 //之后,它指向了新的资源,所以对应这个资源的引用计数加一 SharedPointer<T>& operator=(const SharedPointer<T>& that) { if (this != &that) { removeReference(); this->m_pointer = that.m_pointer; this->m_refCount = that.m_refCount; addReference(); } return *this; } //判断是否指向同一个资源 bool operator==(const SharedPointer<T>& other) { return m_pointer == other.m_pointer; } bool operator!=(const SharedPointer<T>& other) { return !operator==(other); } //指针解引用 T& operator*() const { return *m_pointer; } //调用所知对象的公共成员 T* operator->() const { return m_pointer; } //获取引用计数个数 int GetReferenceCount() const { if (m_refCount) { return *m_refCount; } else { return -1; } } protected: //当为nullpter时,创建引用计数资源,并初始化为1 //否则,引用计数加1。 void addReference() { if (m_refCount) { (*m_refCount)++; } else { m_refCount = new int(0); *m_refCount = 1; } } //引用计数减一,当变为0时,释放所有资源 void removeReference() { if (m_refCount) { (*m_refCount)--; if (*m_refCount == 0) { delete m_refCount; delete m_pointer; m_refCount = 0; m_pointer = 0; } } } private: int * m_refCount; T * m_pointer; };
对上述代码进行测试:
#include <iostream> #include <memory> #include <string> #include <vector> #include "SharedPointer.h" using namespace std; class MyClass { public: ~MyClass() { cout << "释放MyClass(" << _id << ")\n"; } MyClass(int i) :_id(i) { } void Print() const { cout << "MyClass(" << _id << ")" << endl; } private: int _id; }; int main() { { MyClass* px = new MyClass(1); SharedPointer<MyClass> ap(px); SharedPointer<MyClass> bp = ap; SharedPointer<MyClass> cp; cout << "ap的引用计数(2): " << ap.GetReferenceCount() << endl; cout << "将ap赋值给cp\n"; cp = ap; cout << "ap的引用计数(3): " << ap.GetReferenceCount() << endl; MyClass* qx = new MyClass(5); SharedPointer<MyClass> dp(qx); ap = dp; cout << "ap的引用计数(2): " << ap.GetReferenceCount() << endl; cout << "dp的引用计数(2): " << dp.GetReferenceCount() << endl; //"像指针一样使用智能指针" dp->Print(); (*cp).Print(); } cin.get(); }
参考:
https://baike.baidu.com/item/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/10784135?fr=aladdin