众所周知,大家在写代码时,常常会去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;
}
以上就是,我所了解的,智能指针,如何管理和释放目标资源的。
有什么问题,请多多指教。