智能指针的认识和应用

        众所周知,大家在写代码时,常常会去malloc或者new指针,但是常常忘记了释放这些不再使用的资源,虽然这些资源很少,但是计算机中资源也是有限的。如此反复下去,计算机就会很卡,因为没有资源。

        如何解决呢?

        首先我们可以重启,有人说,计算机上百分之80的问题,是靠重启可以解决的,虽然这样可以解决问题,但是,对于服务器而言,这是灭顶之灾,资源不够,服务器会挂掉的。

        智能指针,对于管理资源有什么独特之处呢?

        首先智能指针,是运用了RAII的设计思想。

        RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

        简单来说,就是C++带来的便利,C++的构造函数和析构函数,主要是析构函数(在该对象作用,析构函数不会调用,出了作用域,析构函数会自动执行,去释放对应的资源,这就不需要你再去手动释放了)。

智能指针有以下几类:

std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。 auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原 理

// C++98 管理权转移 auto_ptr
namespace bit
{
 template<class T>
 class auto_ptr
 {
 public:
 auto_ptr(T* ptr)
 :_ptr(ptr)
 {}
 auto_ptr(auto_ptr<T>& sp)
 :_ptr(sp._ptr)
 {
 // 管理权转移
 sp._ptr = nullptr;
 }
 auto_ptr<T>& operator=(auto_ptr<T>& ap)
 {
 // 检测是否为自己给自己赋值
 if (this != &ap)
 {
 // 释放当前对象中资源
 if (_ptr)
 delete _ptr;
 // 转移ap中资源到当前对象中
 _ptr = ap._ptr;
 ap._ptr = NULL;
 }
 return *this;
 }
 ~auto_ptr()
 {
 if (_ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;3.4 std::unique_ptr 
C++11中开始提供更靠谱的unique_ptr 
unique_ptr文档
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原
理
 
 }
 }
 // 像指针一样使用
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 private:
 T* _ptr;
 };
}
// 结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
//int main()
//{
// std::auto_ptr<int> sp1(new int);
// std::auto_ptr<int> sp2(sp1); // 管理权转移
//
// // sp1悬空
// *sp2 = 10;
// cout << *sp2 << endl;
// cout << *sp1 << endl;
// return 0;
//}

观察上述代码,不管什么情况,auto_ptr都会将所管理的对象的指针置给自己,再将管理对象置为nullptr,那么如果别人将目标对象提前拷贝了一份,他的使用在你调用析构函数之后,那不就成为野指针了,通过下列拷贝构造,和赋值重载都可以看出。

 auto_ptr(auto_ptr<T>& sp)
 :_ptr(sp._ptr)
 {
 // 管理权转移
 sp._ptr = nullptr;
 }
 auto_ptr<T>& operator=(auto_ptr<T>& ap)
 {
 // 检测是否为自己给自己赋值
 if (this != &ap)
 {
 // 释放当前对象中资源
 if (_ptr)
 delete _ptr;
 // 转移ap中资源到当前对象中
 _ptr = ap._ptr;
 ap._ptr = NULL;
 }
 return *this;
 }

所以收,很多公司是不会去使用这种智能指针的。

std::unique_ptr

unique_ptr实现原理也很简单,你不是要管理对象,目的就是,防止忘记释放吗?

我就不让其他人去拷贝或者将你的值赋给其他人。引入了关键delete(禁止使用拷贝构造和赋值重载)

// C++11库才更新智能指针实现
// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr
// C++11将boost库中智能指针精华部分吸收了过来
// C++11->unique_ptr/shared_ptr/weak_ptr
// unique_ptr/scoped_ptr
// 原理:简单粗暴 -- 防拷贝
namespace bit
{
 template<class T>
 class unique_ptr
 {
 public:
 unique_ptr(T* ptr)
 :_ptr(ptr)
 {}
 ~unique_ptr() 
3.5 std::shared_ptr 
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr 
std::shared_ptr文档
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如:
比特老师晚上在下班之前都会通知,让最后走的学生记得把门锁下。
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对
象就成野指针了。
 {
 if (_ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
 }
 }
 // 像指针一样使用
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 unique_ptr(const unique_ptr<T>& sp) = delete;
 unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
 private:
 T* _ptr;
 };
}
//int main()
//{
// /*bit::unique_ptr<int> sp1(new int);
// bit::unique_ptr<int> sp2(sp1);*/
//
// std::unique_ptr<int> sp1(new int);
// //std::unique_ptr<int> sp2(sp1);
//
// return 0;
//}

但是不支持都资源访问,就是一个资源只能一个对象访问,不支持多对象访问,shared_ptr就支持多对象访问。 

dstd::shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共 享。 2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。 3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对 象就成野指针了。

注意:shared_ptr中应用了锁,多线程也是可以去运用shared_ptr这就存在资源竞争问题,就会导致资源不一致问题。(简单来说,加锁部分代码,就是临界区资源,需要去保护)


 template<class T>
 class shared_ptr
 {
 public:
 shared_ptr(T* ptr = nullptr)
 :_ptr(ptr)
 , _pRefCount(new int(1))
 , _pmtx(new mutex)
 {}
 shared_ptr(const shared_ptr<T>& sp)
 :_ptr(sp._ptr)
 , _pRefCount(sp._pRefCount)
 , _pmtx(sp._pmtx)
 {
 AddRef();
 }
 void Release()
 {
 _pmtx->lock();
 bool flag = false;
 if (--(*_pRefCount) == 0 && _ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
 delete _pRefCount;
 flag = true;
 }
 _pmtx->unlock();
 if (flag == true)
 {
 delete _pmtx;
 }
 }
 void AddRef()
 {
 _pmtx->lock();
 ++(*_pRefCount);
 _pmtx->unlock();
 }
 shared_ptr<T>& operator=(const shared_ptr<T>& sp)
 {
 //if (this != &sp)
 if (_ptr != sp._ptr)
 {
 Release(); _ptr = sp._ptr;
 _pRefCount = sp._pRefCount;
 _pmtx = sp._pmtx;
 AddRef();
 }
 return *this;
 }
 int use_count()
 {
 return *_pRefCount;
 }
 ~shared_ptr()
 {
 Release();
 }
 // 像指针一样使用
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 T* get() const
 {
 return _ptr;
 }
 private:
 T* _ptr;
 int* _pRefCount;
 mutex* _pmtx;
 };

shared_ptr可以更高效的管理资源,因为里面有计数器,计数器为0,就表明不需要该资源了,可以释放。

但是,上面有一个缺陷,就是循环引用,对于循环引用,shared_ptr就显得不知所措了。

于是,就设计出了,weak_ptr,就是去解决shared_ptr循环引用问题的。

    // 简化版本的weak_ptr实现
 template<class T>
 class weak_ptr
 {
 public:
 weak_ptr()
 :_ptr(nullptr)
 {}
 weak_ptr(const shared_ptr<T>& sp)
 :_ptr(sp.get())
 {}
 weak_ptr<T>& operator=(const shared_ptr<T>& sp)
 {
 _ptr = sp.get();
 return *this;std::shared_ptr的线程安全问题 
通过下面的程序我们来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分
为两方面:
1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时
++或--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错
乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、--是需要加锁
的,也就是说引用计数的操作是线程安全的。
2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
 }
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 private:
 T* _ptr;
 };

// shared_ptr智能指针是线程安全的吗?
// 是的,引用计数的加减是加锁保护的。但是指向资源不是线程安全的
// 指向堆上资源的线程安全问题是访问的人处理的,智能指针不管,也管不了
// 引用计数的线程安全问题,是智能指针要处理的
//int main()
//{
// bit::shared_ptr<int> sp1(new int);
// bit::shared_ptr<int> sp2(sp1);
// bit::shared_ptr<int> sp3(sp1);
//
// bit::shared_ptr<int> sp4(new int);
// bit::shared_ptr<int> sp5(sp4);
//
// //sp1 = sp1;
// //sp1 = sp2;
//
// //sp1 = sp4;
// //sp2 = sp4;
// //sp3 = sp4;
//
// *sp1 = 2;
// *sp2 = 3;
//
// return 0;
//}

由上面代码可以看出,weak_ptr是不能能单独使用的,需要配合shared_ptr去使用,从上面的拷贝构造和赋值重载就可以看出,它只支持shared_ptr赋值和拷贝给他。

shared_ptr循环引用问题

struct ListNode
{
 int _data;循环引用分析: 
1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动
delete。
2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上
一个节点。
4. 也就是说_next析构了,node2就释放了。
5. 也就是说_prev析构了,node1就释放了。
6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev
属于node2成员,所以这就叫循环引用,谁也不会释放。
 shared_ptr<ListNode> _prev;
 shared_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0;
}

 使用weak_ptr去解决,weak_ptr不会造成shared_ptr引用计数器++。

// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和
_prev不会增加node1和node2的引用计数。
struct ListNode如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这
个问题(ps:删除器这个问题我们了解一下)
 
{
 int _data;
 weak_ptr<ListNode> _prev;
 weak_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0;
}

定制删除器 

如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这 个问题,shared_ptr还支持出入仿函数去释放所创建的资源,其实就是,在析构函数中调用所传进来的仿函数。

// 仿函数的删除器
template<class T>
struct FreeFunc {
 void operator()(T* ptr)
 {
 cout << "free:" << ptr << endl;
 free(ptr);
 }
};
template<class T>
struct DeleteArrayFunc {
 void operator()(T* ptr)
 { 
 cout << "delete[]" << ptr << endl;
 delete[] ptr; 
 }
};
int main()
{
 FreeFunc<int> freeFunc;
 std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);
 DeleteArrayFunc<int> deleteArrayFunc;
 std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
    
    
 std::shared_ptr<A> sp4(new A[10], [](A* p){delete[] p; }); 
4.C++11和boost中智能指针的关系 
1. C++ 98 中产生了第一个智能指针auto_ptr.
2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost
的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
 std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p)
{fclose(p); });
 
 return 0;
}

以上就是,我所了解的,智能指针,如何管理和释放目标资源的。

有什么问题,请多多指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

挣扎的泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值