前一段时间,有不少朋友问我关于智能指针的问题,并且反映经常会在面试中被面试官问到,所以今天小豆君就来讲讲我对智能指针的理解,希望能对大家有所帮助
既然讲智能指针,我们就先来看看它为什么会出现。
1 传统指针存在的问题
刚学C++的朋友,要数最令人头疼的问题莫过于指针了。
当你在堆上创建了一个对象时,系统就把这个对象的生命期完全交给了你,当用完之后,系统并不会回收资源,而是需要你来释放它。
那么,既然要负责对象的释放问题,就要知道什么时候释放和在哪里释放。如果你没有处理好这两个问题,就会造成内存泄漏或程序崩溃的问题。
//1 内存泄漏 str1所指的资源没有被释放 { string* str1 = new string("hello"); string* str2 = new string("world"); } //2 多重释放,引起程序崩溃 { string* str1 = new string("hello"); delete str1; //... delete str1;//引起程序崩溃 }
虽然上述例子很简单,指针的释放是在同一作用域中进行的,大家在编程中也能很容易避免上述代码中的问题。
但是,对于一个大型项目,在某一处创建的对象,可能并不会在对应作用域中释放,而是等到某些事件发生,异常处理等情况下才会去销毁对象,对于这样的问题往往是很难排查出来的。
所以,有必要引用一种机制来负责指针的自动销毁。而不是由程序员本身去手动销毁。智能指针恰恰就是这样的一种机制。
2 基于引用计数的智能指针原理分析
下面的这张图,解释了智能指针的原理。
1.当从堆上申请了一个资源时,我们就创建一个智能指针对象,使它指向这个资源,同时,在堆上申请一个用于计数的资源,让后来所有的指向该资源的对象都共享这个计数资源,这样,引用计数的个数就只有一份。
2.当将ptr1对象赋值给对象ptr2时,其共享的引用计数变为2。
3.删除ptr2对象时,其对应的引用计数减为1。
4.删除ptr1对象时,引用计数变为0,则释放资源。
3 智能指针的一种实现方式
在写代码前,先明确下代码的目的:
首先,智能指针是一个类,这样就可以使用构造函数和析构函数对引用计数进行维护;
其次,它要表现出指针的行为,并且使用起来也要像普通指针一样;
最后,智能指针对任何类型都可以使用,所以它应该是一个模板。
在阅读代码的时候,可以参考智能指针原理图。 代码并不困难,并且小豆君都做了注释
#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; };
4 测试
以下程序是对上述代码的测试,大家可以看看实际的运行效果
#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(); }
运行结果:
我们跟踪测试程序,通过运行结果,查看引用计数的变化,并且,当计数为0时,自动释放资源。
好了,关于智能指针的原理示例程序就给大家介绍到这里了,以后再有面试官问你,就可以明明白白的给他解释了。其实,标准库中也有对应库实现shared_ptr,而且它比小豆君的要复杂多了。
智能指针虽然在一定程序上解决了裸指针存在的问题,但是还是有很多不足的地方,例如本例还缺少资源共享时的保护措施,环形引用等问题,等有机会小豆君再跟大家分享吧。
欢迎关注微信公众号-小豆君Qt分享