为什么会出现智能指针??
在编写C++程序代码的过程中,往往都会涉及到堆内存的开辟和释放,使用new和delete关键字,特别是堆内存上的释放是通过程序员手动完成的,它不像栈内存上,只要生存周期结束了即可由系统自动回收。
但是很多粗心的程序员,由于各种情况可能忘记手动释放堆内存或因为一些细节原因而未进行堆内存的释放,最终导致产生大量的内存泄漏,造成资源浪费,因此智能指针出来了。
什么是智能指针?
智能指针:顾名思义就是自动化,智能的管理指针所指向的动态开辟资源的释放。
智能指针就是一个类,它将裸指针(带*的指针)进行了封装,实现了指针的自动释放。
它的高明之处在于:程序员只需要一次性设计出一个具有良好的功能的智能指针类,用它实例出的对象会自动对对象内存的堆资源进行管理, 而不需要程序员去干涉,它就自己可以很好的完成对堆内存的管理。
智能指针需要具备三要素:(需要我们设计的内容)
①RALL ②具备指针的功能 ③能够拷贝和赋值
RALL技术: | 资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成分配和初始化;在析构函数完成资源的释放 |
指针功能: | 例如:解引用 * 和指向操作 -> ,等等 |
拷贝和赋值: | 这个两个功能是指针所需要具备的(注意浅拷贝和深拷贝) |
使用智能指针的前提条件:利用了当栈对象的生存周期结束时,会自动调用析构函数,来进行对对象的销毁。 RTTI技术。智能指针不能 在堆上创建。
哪些特殊的情况会导致堆内存没有机会得到释放?
》1:由于程序中的某些逻辑语句满足就退出了程序,最终导致了堆内存资源未得到释放。
int main(){
int* p = new int;
if(条件表达式){
return 0;
}
delete p;
}
》2:对已经释放的指针,再一次进行释放,会导致程序出错。
void fun(int* p){
delete p;
p = NULL;
}
int main(){
int* p = new int;
fun(p);
delete p;
}
》3:在上述代码块中,由于try块中捕捉到了异常,直接执行catch中的语句,而未进行堆内存的释放,最终也导致了内存泄漏。
int Div(int a,int b){
if(b==0){
throw "div by zero condition!";
}
return a/b;
}
int main(){
int *p = new int;
try{
Div(10,0);
}
catch(const char* meg){
cerr<<msg<<endl;
}
delete p;
}
智能指针的诞生解放了C++程序员对于堆内存的管理。
设计智能指针的类模板:
需要解决的问题:
指针可以做的事,智能指针也必须可以做。需要对* , ->运算符进行重载。
(1)* 运算符需返回引用,因为*可以连续使用
(2)* 和 -> 的重载函数是没有形式参数的。
template<typename T>
class SmartPtr{
public://如果我们不给这个public,它默认的是private,然后就会出现不能调用构造函数的错误
SmartPtr(T* ptr = NULL) :_ptr(ptr){}
~SmartPtr() { delete _ptr; _ptr = NULL; }
T& operator*() {
return *_ptr;
}
//一个const修饰返回值也就是*_ptr不变//第二个const修饰this的指向的不变_ptr不变。
const T& operator*()const {
return *_ptr;
}
T* operator->() {
return _ptr;
}
const T* operator->()const {
return _ptr;
}
private:
T * _ptr;
};
int main() {
SmartPtr<int> p(new int(10));
*p = 20;
cout << "*p = " << *p << endl;
return 0;
}
常见的智能指针的浅拷贝问题
int main(){
SmartPtr<int> p(new int);
*p = 20;
SmartPtr<int> p1(p);
//程序意图使得p和p1指向同一块堆内存 new int
cout<<"*p="<<*p<<endl;
}
然而执行结果出错了,在Linux直接检测出了内存泄漏。
错误的原因:两个指针对象中的_ptr指向了堆内存的同一块内存区域,但是p1对象析构时释放了该堆内存,而对象p析构时又对该块堆内存释放了一遍,所以导致了出错。
解决浅拷贝方案:引用计数
思想:引用计数实际上就是为了解决这种浅拷贝问题而诞生的,每当对资源(堆内存 )引用一次就是对计数器+1,每当删除一次,就对计数器减一,直到当 资源的引用计数为0时,就证明没有对象对它进行引用了,此时调用析构函数对资源进行释放。
管理资源的引用计数:
class resCountMap {
public:
//增加资源的引用计数
void addRef(void* ptr) {
_resCntMap[ptr] += 1;
}
//减少资源的引用计数
void delRef(void* ptr) {
//要考虑到线程安全问题,因为这不是一个原子操作,需要加锁
if (_resCntMap[ptr] == 0)
_resCntMap.erase(ptr);
else
_resCntMap[ptr] -= 1;
}
//获取资源的引用计数
int getRef(void* ptr)
{
return _resCntMap[ptr];
}
private:
map<void*, int> _resCntMap;
};
具有引用计数的智能指针
mutex m;
template<typename T>
class smart_ptr
{
public:
smart_ptr(T* p = NULL)
{
_ptr = p;
_map.insert(make_pair(_ptr, 1));
}
~smart_ptr()//析构
{
m.lock();
if (--_map[_ptr] <= 0 && NULL != _ptr)
{
delete _ptr;
_map.erase(_ptr);
_ptr = NULL;
}
m.unlock();
}
smart_ptr(const smart_ptr<T> &src)//拷贝构造
{
m.lock();
_ptr = src._ptr;
++_map[_ptr];
m.unlock();
}
smart_ptr& operator=(const smart_ptr<T> &src)//赋值运算
{
if (_ptr == src._ptr)
{
return *this;
}
m.lock();
if (--_map[_ptr] <= 0 && NULL != _ptr)
{
delete _ptr;
_map.erase(_ptr);
_ptr = NULL;
}
_ptr = src._ptr;
++_map[_ptr];
m.unlock();
}
T* get_ptr()
{
return _ptr;
}
T& smart_ptr<T>::operator*()
{
return *_ptr;
}
T* smart_ptr<T>::operator->()//A.operator->()->
{
return _ptr;
}
private:
T * _ptr;
static map<T*, int> _map;//存储引用计数的map表
friend class mweak_ptr<T>;
};
template<typename T>
map<T*, int> smart_ptr<T>::_map;