本文重点:
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对象之间的共享资源;
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享;
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一;
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是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; // 互斥锁
};