C++ 智能指针

本文重点:

1.智能指针的概念;
2.智能指针的使用及原理;

一.为什么需要智能指针:

在C/C++中指针引发的错误有如下两种:内存泄漏和抛异常的问题,而使用智能指针可以很好的解决这两个问题;

1.1内存泄漏:

有以下几种情况会导致内存泄漏:
(1)动态开辟内存空间后未能及时释放,导致一直占据着该内存单元,直到程序结束后自动释放,它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃;

(2)由于疏忽导致未能释放已经不用的内存情况;
因此内存泄漏是资源泄漏中的一种,资源泄漏的另外一种是句柄泄漏,例如文件读写,socket操作等都有可能导致句柄的泄漏;

1.2悬垂指针:

悬垂指针也叫野指针,是未初始化或未清零的指针;与空指针(NULL)不同,悬挂指针无法通过简单地判断是否为 NULL避免;

关于悬垂指针出现的几种情况:
(1)指针没有被初始化;
(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针;

关于内存泄漏的实例:
(1)忘记释放导致内存泄漏:
#include <iostream>
using namespace std;

class Test
{
public:
    Test(int a = 0 )
    : _str(a)
    {}

    ~Test( )
    {
        cout<<"Calling destructor"<<endl;
    }
public:
    int _str;
};

int main()
{
    Test *t1 = new Test(3);
    cout << t1->_str << endl;
//  delete t1;      //使用完后必须释放;  
    return 0;
}
(2)抛异常----->异常安全问题:
#include <iostream>
using namespace std;

class Test
{
public:
    Test(int a = 0 )
    : _str(a)
    {}

    ~Test( )
    {
        cout<<"Calling destructor"<<endl;
    }
public:
    int _str;
};

int main()
{
    try
    {
        Test *t1 = new Test(3);
        cout << t1->_str << endl;

        throw("an exception");

        delete t1;  
    }
    catch(...)
    {
        cout << "Something has gone wrong" << endl;
    }

    return 0;
}

二.RAII及简单的智能指针实现原理:

1.RAII:

RAII利用对象生命周期来控制资源释放;在对象构造时获取资源,接着控制对象对资源的访问,使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源;

这种做法有两大好处:
(1)不需要显式地释放资源;
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效;

注意:智能指针不等价于RAII,智能指针是RAII思想的一种实现;


//RAII思想解决内存泄漏问题:

#include <iostream>
using namespace std;
template <class T>
class SmartPtr{
public:
	SmartPtr(T* ptr)
		:_ptr(*ptr)    //把指针保存起来;
	{}

	~SmartPtr(){
		cout<<"delete:"<<_ptr<<endl;
		delete _ptr;
	}

private:
	T* _ptr;	
};

int main(){
	int *p =new int;
	SmartPtr<int> sp(p);    //实现托管行为----没有进行delete也能进行释放,出了作用域会调用析构函数;
	
	return 0;
}
	

//RAII实现抛异常的处理问题:

#include <iostream>
using namespace std;
template <class T>
class SmartPtr{
public:
	SmartPtr(T* ptr)
		:_ptr(*ptr)    //把指针保存起来;
	{}

	~SmartPtr(){
		cout<<"delete:"<<_ptr<<endl;
		delete _ptr;
	}

private:
	T* _ptr;	
};

//抛异常的实现过程:
void  Div(double a, double b){
	if (b == 0){
		throw invalid_argument("除数等于0");
	}

	else{
		cout << a / b << endl;
	}
}
void Func(){
	int * p = new int;
	SmartPtr<int> sp(p);     //托管释放---》此时就不需要进行delete释放资源;
	
	double x1, x2;
	cin >> x1 >> x2;
	Div(x1, x2);

	/*cout << "delete:" << p << endl;
	delete p;*/
}

int main(){
	try{
		Func();
	}

	catch (exception &e){
		e.what();
	}
	return 0}

2.智能指针实现原理:

(1)智能指针的作用:某种程度上弥补了C++中没有垃圾回收器的缺陷;
(2)具备智能指针的条件:RAII思想;像指针一样(*解引用 / ->解引用);


#include <iostream>
using namespace std;

template<class T> 
class SmartPtr {
public:    
	SmartPtr(T* ptr = nullptr) 
		: _ptr(ptr)    
	{}

	~SmartPtr(){ 
		if (_ptr){
			delete _ptr;
		}
	}        
	T& operator*() { 
		return *_ptr; 
	}    
	
	T* operator->(){ 
		return _ptr; 
	} 
	
private:
	T* _ptr;
};

struct Date { 
	int _year;    
	int _month;    
	int _day; 
};

int main() {
	SmartPtr<int> sp1(new int);    
	*sp1 = 10;    
	cout << *sp1 << endl;   
	SmartPtr<int> sparray(new Date);    
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;    
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->    
	sparray->_year = 2018;    
	sparray->_month = 1;    
	sparray->_day = 1; 

	return 0;
}   
 

三.智能指针的发展及分类;

1.std::auto_ptr:C++98版本中提供了auto_ptr的智能指针;

std::auto_ptr有一个致命的缺陷:当对象进行赋值或拷贝之后,前面的对象就悬空了;
(1)关于auto_ptr的使用及问题:


#include <memory>
 
class Date { 
public:    
   Date() { 
      cout << "Date()" << endl;
   }    
   ~Date(){ 
      cout << "~Date()" << endl;
   }
 
    int _year;    
    int _month;    
    int _day; 
};
 
int main() {
    auto_ptr<Date> ap(new Date); 
    auto_ptr<Date> copy(ap);
 
    // auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了    
    // C++98中设计的auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptr ;   
    ap->_year = 2018;
 
    return 0; 
 }

编译成功,但程序会异常终止;

(2)关于std::auto_ptr的实现原理:管理权转移的思想;


#include <iostream>
using namespace std;
class Date {
public:    
	Date() { cout << "Date()" << endl; }    
	~Date(){ cout << "~Date()" << endl; }

    int _year;    
	int _month;    
	int _day;

};

template<class T> 
class AutoPtr { 
public:    
	AutoPtr(T* ptr = NULL)        
		: _ptr(ptr)    
	{}        
	
	~AutoPtr(){
		cout<<"delete: "<< _ptr;
		delete _ptr;
	}        
	// 一旦发生拷贝,就将ap中资源转移到当前对象中,然后ap与其所管理资源断开联系,    
	// 这样就解决了一块空间被多个对象使用而造成程序奔溃问题;   
	
	AutoPtr(AutoPtr<T>& ap)        
	: _ptr(ap._ptr){         
		ap._ptr = NULL;    
	}        
	
	AutoPtr<T>& operator=(AutoPtr<T>& ap){        
		// 检测是否为自己给自己赋值        
		if(this != &a){            
		// 释放当前对象中资源            
		if (_ptr){
			delete _ptr;
		}
		// 转移ap中资源到当前对象中            
		_ptr = ap._ptr;            
		ap._ptr = NULL;        
	}                
		return *this;    
	}    
    
    T& operator*() {
		return *_ptr;
	} 

	T* operator->() { 
		return _ptr; 
	} 
private:    
	T* _ptr;
};

int main() {
	AutoPtr<Date> ap(new Date);        
	// 现在再从实现原理层来分析会发现,这里拷贝后把ap对象的指针赋空了,导致ap对象悬空   
	// 通过ap对象访问资源时就会出现问题。    
	AutoPtr<Date> copy(ap);    
	
	ap->_year = 2018;

	return 0;
}
2.std::unique_ptr:C++11开始提供了更靠谱的std::unique_ptr智能指针;

std::unique_ptr的设计思路主要实现的功能:防拷贝;-------不让对象进行拷贝和赋值;
此外需要注意:unique_ptr是线程安全的;

关于std::unique_ptr的实现原理:

template<class T> 
class UniquePtr { 
public:    
	UniquePtr(T * ptr = nullptr)         
		: _ptr(ptr)    
	{}
 
     ~UniquePtr(){        
     	cout<<"delete: "<< _ptr <<endl;
     	
     	delete _ptr;    
     }
 
    T& operator*() {
    	return *_ptr;
    }
    
    T* operator->() {
    	return _ptr;
    }

private:    
// C++98防拷贝的方式:只声明不实现+声明成私有    
	UniquePtr(UniquePtr<T> const &);    
	UniquePtr & operator=(UniquePtr<T> const &);         

// C++11防拷贝的方式:delete    
	UniquePtr(UniquePtr<T> const &) = delete;    
	UniquePtr & operator=(UniquePtr<T> const &) = delete;    

private:    
	T * _ptr; 

};


3.std::shared_ptr 可以支持拷贝;

需要注意的是std::shared_ptr引用计数是线程安全的;但是管理的资源不是线程安全的,需要进行加锁操作;

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

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

关于shared_ptr的原理实现:

#include <thread> 
#include <mutex>
 
template <class T> 
class SharedPtr { 
public:    
	SharedPtr(T* ptr = nullptr) 
		 : _ptr(ptr)        
		 , _pRefCount(new int(1))        
		 , _pMutex(new mutex)   
	{}
 
    ~SharedPtr() {
    	Release();
    }
 
    SharedPtr(const SharedPtr<T>& sp)        
    	: _ptr(sp._ptr)        
    	, _pRefCount(sp._pRefCount)        
    	, _pMutex(sp._pMutex)    
    	{            
    	AddRefCount();    
    	}
    	
    	// sp1 = sp2    
    	
    	SharedPtr<T>& operator=(const SharedPtr<T>& sp)    {        
    	//if (this != &sp)        
    	if (_ptr != sp._ptr){            
    	// 释放管理的旧资源            
    		Release();
        // 共享管理新对象的资源,并增加引用计数            
        	_ptr = sp._ptr;            
        	_pRefCount = sp._pRefCount;            
        	_pMutex = sp._pMutex;
        	AddRefCount();        
        }
 
        return *this;    
       }
 
    T& operator*() {
    	return *_ptr;
    }    

	T* operator->() {
		return _ptr;
	}
 
    int UseCount() {
    	return *_pRefCount;
    }    

	T* Get() { 
		return _ptr; 
	}
 
    void AddRefCount(){        
    // 加锁或者使用加1的原子操作        
    	_pMutex->lock();        
    	++(*_pRefCount);        
    	_pMutex->unlock();    
    } 
 private:    
 	void Release(){
        bool deleteflag = false; 
        // 引用计数减1,如果减到0,则释放资源        
        _pMutex.lock();        
        if (--(*_pRefCount) == 0){            
        	delete _ptr;            
        	delete _pRefCount;             
        	deleteflag = true;        
        }        
        _pMutex.unlock();                
        if(deleteflag == true){            
        	delete _pMutex;    
        	}
     } 
 
 private:    
 	int* _pRefCount; // 引用计数    
 	T*   _ptr;       // 指向管理资源的指针       
 	mutex* _pMutex;      // 互斥锁 
};
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值