在说智能指针之前,我们先来看以下代码:
//文件——Smart_ptr.h
#include <iostream>
#include <vld.h>
using namespace std;
class Test
{
public:
Test(int a = 10,int* p = NULL):ma(a),mp(p) {cout<<"Test()"<<endl;}
~Test() {cout<<"~Test()"<<endl;}
private:
int ma;
int* mp;
};
//文件——Smart_ptr.cpp
#include "SmartPtr.h"
int main()
{
int *p = new int();
Test A(20,p);
return 0;
}
我们使用vld工具去查看程序运行过程中是否出现内存泄露,结果如下:
在上图中可以看到,确实发生了内存泄露。因为我们传入的指针指向的是堆上的内存块,如果在对象析构时不手动释放(delete)的话,就会发生内存泄露。那你或许会说——实现自己的析构函数、进行手动释放不就行了,答案是不行,因为当你传入的p指向的是栈上内存时,就不能使用delete去手动释放,栈上内存是系统来进行管理的。对于指针p传入的不确定性,我们就没办法解决了吗?
答案就是——智能指针。我们都知道,栈上空间是系统分配、回收的,堆上空间是用户自己分配、回收的,那么如果能对堆上空间实现用户分配、系统自行回收的机制,就不用担心内存泄露的问题了。
智能指针的思想:定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数中完成资源的清理和释放,可以保证资源的正确使用和释放。(资源指的是堆上资源)
我们将讲述四个智能指针,其中scoped_ptr、shared_ptr和 weak_ptr是C++11标准从boost库引入的智能指针。
一、auto_ptr
auto_ptr已经被摒弃了,只是为了向前兼容而被保存了下来,建议代码中不要使用该智能指针。
1.实现一
#include <iostream>
#include <vld.h>
using namespace std;
template <typename T>
class Myauto_ptr
{
public:
Myauto_ptr(T* p):_mptr(p){}//构造函数
Myauto_ptr(const Myauto_ptr<T>& p)//拷贝构造函数
{
_mptr = p._mptr;
p._mptr = NULL;
}
Myauto_ptr<T>& operator=(const Myauto_ptr<T>& p)//复制运算符的可重载函数
{
if(this != &p)
{
_mptr = p._mptr;
p._mptr = NULL;
}
return *this;
}
T& operator*()//*运算符的可重载函数
{
return *_mptr;
}
T* operator->()//->运算符的可重载函数
{
return _mptr;
}
~Myauto_ptr()
{
delete _mptr;
_mptr = NULL;
}
private:
T* _mptr;
};
class Test
{
public:
void Show()
{
cout<<"Test::show()"<<endl;
}
};
Myauto_ptr<int> fun()
{
Myauto_ptr<int> tmp(new int(80));
return tmp;
}
int main()
{
Myauto_ptr<int> pa(new int);
*pa = 20;
cout<<*pa<<endl;
Myauto_ptr<int> pb(pa);
*pb = 50;
cout<<*pb<<endl;
Myauto_ptr<int> pc(fun());//自定义类型的临时对象是 变量
cout<<*pc<<endl;
Myauto_ptr<Test> pd(new Test());
pd->Show();
return 0;
}
2.实现一的问题
auto_ptr的原则是“管理权唯一,释放权唯一”。
问题一:拷贝构造函数
A指针指向了addr内存,B指针通过 拷贝构造的方法 将A指针对addr的管理权和释放权一并拿来,而A指针这时再想去操作addr内存时,却只能崩溃(它的_mptr已经变为NULL),这样非常不合理,A是去构造B的,自己却失去了所有东西......
问题二:赋值运算符的可重载函数
A指针和B指针 都初始化指向addr内存,当用B指针去给A指针赋值时,按照步骤先将A指向的内存释放掉,A再重新指向B指向的内存,最后将B的指向置为NULL,但是A和B管理的是同一片内存,释放掉之后,B指针就是一个野指针,A得到之后去访问 或者 析构时程序就会崩溃。
3.实现二
原则是“管理权不唯一,释放权唯一”,即可以多个auto_ptr管理同一片内存,但是只能有一个指针释放该片内存。怎么确定是哪个指针能释放呢?在成员变量中加入一个标志变量flag,flag为true则有释放权,flag为false则没有释放权。
管理权转移原则:
① 旧智能指针有释放权,新智能指针有,并且旧的置为没有
② 旧智能指针没有释放权,新智能指针也没有
#include <iostream>
using namespace std;
template <typename T>
class Myauto_ptr2
{
public:
Myauto_ptr2(T* p)
{
_mptr = p;
_flag = true;
if(!_mptr)//_mptr==NULL
{
_flag = false;
}
}
Myauto_ptr2(const Myauto_ptr2<T>& p)
{
_mptr = p._mptr;
if(p._flag == true)
{
_flag = true;
p.realease();
}
else
{
_flag = false;
}
}
Myauto_ptr2<T>& operator=(const Myauto_ptr2<T>& p)
{
if(this != &p)
{
this->~Myauto_ptr2();//释放旧资源!!
_mptr = p._mptr;
if(p._flag == true)
{
_flag = true;//释放权转移
p.realease();
}
else
{
_flag = false;
}
}
return *this;
}
T& operator*()
{
return *_mptr;
}
T* operator->()
{
return _mptr;
}
~Myauto_ptr2()
{
if(_flag == true)
{
delete _mptr;
_mptr = NULL;
_flag = false;
}
}
private:
void realease()const
{
((Myauto_ptr2<T>*)this)->_flag = false;
}
T* _mptr;
bool _flag;//mutable bool _flag; mutable 关键字保证了_flag就算是const对象中的成员,也可以被修改
};
4.实现二的问题
根据管理权原则一,若旧智能指针有释放权,则新智能指针将释放权拿走,旧释放权false。那么如果这个新智能指针只是一个临时量,随着生存周期的到来也随之消失,那块被管理的内存岂不是莫名其妙就被释放了?
问题一:智能指针作为函数的形参——传递实参的过程是初始化过程,可能发生释放权的转移,从而造成智能指针的提前释放
void test(Myauto_ptr2<int> rhs)
{
cout<<*rhs<<endl;
}
int main()
{
Myauto_ptr2<int> p(new int(10));
Myauto_ptr2<int> q(new int(20));
*p = 11;
*q = 22;
p = q;
Myauto_ptr2<int> w = p;
test(w);//在传参的过程中,w的释放权发生转移
cout<<"w"<<*w<<endl;//打印一个随机值
return 0;
}
二、scoped_ptr
在auto_ptr中,因为设计的问题,导致在使用拷贝构造函数和 赋值运算符的可重载函数时,发生了访问 空指针、野指针的情况。为了规避这些情况,scoped_str在设计的时候就直接屏蔽了 拷贝构造函数 和 赋值运算符的可重载函数。scoped_ptr的原则——管理权唯一,释放权唯一。
1. 实现
#include <iostream>
using namespace std;
template <typename T>
class Myscoped_str
{
public:
Myscoped_str(T* p):_mptr(p){}
T& operator*()
{
return *_mptr;
}
T* operator->()
{
return _mptr;
}
~Myscoped_str()
{
delete _mptr;
_mptr = NULL;
}
private:
Myscoped_str(const Myscoped_str<T>& rhs);
Myscoped_str<T>& operator=(const Myscoped_str<T>& rhs);
T* _mptr;
};
2. 问题
scoped_ptr的原则也是“管理权唯一,释放权唯一”,因为屏蔽了 拷贝构造函数 和 赋值运算符的可重载函数,所以不可能通过这两种方式去 使得两个及以上的指针指向同一片内存,但是仍然避免不了人为的操作,即初始化两个scope_ptr指向同一片内存,当其中一个指针释放了该内存,另一个再想去修改内存的内容的话程序就会崩溃。
三、shared_ptr
shared_ptr是基于“引用计数”实现的,多个shared_ptr可指向同一片内存,并且它们维护着共享的引用计数器——记录着指向同一片内存的shared_ptr对象的个数。当最后一个指向某一片内存的shared_ptr对象也销毁之后,会自动销毁掉该片内存。shared_ptr很好地解决了 多个智能指针指向一块内存块时, 析构时程序崩溃 的情况。shared_ptr的原则是——管理权不唯一,释放权不唯一。
1. 引用计数器
我们只需要一个 引用计数器对象,这个对象中就存储了所有关于内存引用的信息,对于引用计数器的类设计就采用 单例模式。
class Refmanage
{
public:
static Refmanage* GetSingle()
//向外提供的 得到唯一引用计数器对象 的方法
{
return &_rm;
}
public:
void addref(void* ptr)
{
//判空
if(ptr != NULL)
{
int index = Find(ptr);//在引用计数器中找一下改地址是否已有
if(index < 0)//没找到
{
Node tmp(ptr,1);//新创结点
_arr[_length++] = tmp;//插入引用计数器
}
else//找到该地址
{
_arr[index]._count++;
}
}
}
int getref(void* ptr)
{
//首先一定记得判空
if(ptr == NULL)
{
return 0;
}
int index = Find(ptr);
if(index < 0 )
{
return -1;
}
return _arr[index]._count;
}
void delref(void* ptr)//减少计数
{
if(ptr != NULL)
{
int index = Find(ptr);
if(index < 0)
{
throw exception("addr is not exist");
}
else
{
if(_arr[index]._count != 0)
{
_arr[index]._count--;
}
}
}
}
private:
Refmanage():_length(0)
{}
Refmanage(const Refmanage& rhs);
private:
int Find(void* ptr)
{
for(int i=0; i<_length; i++)
{
if(_arr[i]._addr == ptr)
{
return i;//返回 addr的位置
}
}
return -1;
}
class Node
{
public:
Node(void* ptr=NULL, int num = 0):_addr(ptr),_count(num){}
~Node(){memset(this,0,sizeof(Node));}
public:
void* _addr;//引用的内存地址
int _count; //该内存的引用数
};
Node _arr[10];
int _length;// 引用计数器的有效长度
static Refmanage _rm; //唯一的引用计数器对象
};
2. shared_ptr 实现
Refmanage Refmanage::_rm; //前置声明,否则 static Refmanage* rm 编译器不认识
template <typename T>
class Myshared_ptr
{
public:
Myshared_ptr(T* ptr=NULL):_mptr(ptr)
{
AddRef();
}
Myshared_ptr(const Myshared_ptr<T>& rhs):_mptr(ptr)
{
AddRef();
}
Myshared_ptr& operator=(const Myshared_ptr<T>& rhs)
{
if(this != &rhs)
{
DelRef();
this->~Myshared_ptr();
_mptr = rhs._mptr;
rhs.AddRef();
}
return *this;
}
~Myshared_ptr()
{
DelRef();
if(GetRef() == 0)
{
delete _mptr;
}
_mptr = NULL;
}
T& operator*()
{
return *_mptr;
}
T* operator->()
{
return _mptr;
}
private:
void AddRef()
{
rm->addref(_mptr);
}
void DelRef()
{
rm->delref(_mptr);
}
int GetRef()
{
return rm->getref(_mptr);
}
T* _mptr;
static Refmanage* rm; //指针
};
//静态成员变量类外初始化
template <typename T>
Refmanage* Myshared_ptr<T>::rm = Refmanage::GetSingle();

3. 问题
虽然shared_ptr很好地解决了 “多个智能指针指向同一块内存时,析构时出错”的问题,可是同时也带来了新的问题。shared_ptr之间不能相互引用,否则管理的堆上内存块无法释放。class B;
class A
{
public:
A()
{
cout<<"A::A()"<<endl;
}
~A()
{
cout<<"A::~A()"<<endl;
}
Myshared_ptr<B> pb; //成员对象
};
class B
{
public:
B()
{
cout<<"B::B()"<<endl;
}
~B()
{
cout<<"B::~B()"<<endl;
}
Myshared_ptr<A> pa; //成员对象
};
int main()
{
Myshared_ptr<A> a(new A());
Myshared_ptr<B> b(new B());
a->pb = b;
b->pa = a;
return 0;
}

图解:
四、weak_ptr
weak_ptr的出现就是为了 解决shared_ptr中“不能相互引用”的问题,因此weak_ptr需要和shared_ptr配合使用。weak_ptr是弱引用,而shared_ptr是一个强引用。弱引用:弱引用只是当对象存在时的一个引用,对象不存在时弱引用能够检测出来,从而避免非法访问。弱引用并不像强引用那样可以进行内存管理(对引用计数没有影响),它的特点就是可以检测到 对象已经不存在。
1. 实现
class Refmanage
{
public:
static Refmanage* GetSingle()
//向外提供的 得到唯一引用计数器对象 的方法
{
return &_rm;
}
public:
void addref(void* ptr)
{
//判空
if(ptr != NULL)
{
int index = Find(ptr);//在引用计数器中找一下改地址是否已有
if(index < 0)//没找到
{
Node tmp(ptr,1);//新创结点
_arr[_length++] = tmp;//插入引用计数器
}
else//找到该地址
{
_arr[index]._count++;
}
}
}
int getref(void* ptr)
{
//首先一定记得判空
if(ptr == NULL)
{
return 0;
}
int index = Find(ptr);
if(index < 0 )
{
return -1;
}
return _arr[index]._count;
}
void delref(void* ptr)//减少计数
{
if(ptr != NULL)
{
int index = Find(ptr);
if(index < 0)
{
throw exception("addr is not exist");
}
else
{
if(_arr[index]._count != 0)
{
_arr[index]._count--;
}
}
}
}
private:
Refmanage():_length(0)
{}
Refmanage(const Refmanage& rhs);
private:
int Find(void* ptr)
{
for(int i=0; i<_length; i++)
{
if(_arr[i]._addr == ptr)
{
return i;//返回 addr的位置
}
}
return -1;
}
class Node
{
public:
Node(void* ptr=NULL, int num = 0):_addr(ptr),_count(num){}
~Node(){memset(this,0,sizeof(Node));}
public:
void* _addr;//引用的内存地址
int _count; //该内存的引用数
};
Node _arr[10];
int _length;// 引用计数器的有效长度
static Refmanage _rm; //唯一的引用计数器对象
};
Refmanage Refmanage::_rm;
template <typename T>
class Myshared_ptr
{
public:
Myshared_ptr(T* ptr=NULL):_mptr(ptr)
{
AddRef();
}
Myshared_ptr(const Myshared_ptr<T>& rhs):_mptr(ptr)
{
AddRef();
}
Myshared_ptr& operator=(const Myshared_ptr<T>& rhs)
{
if(this != &rhs)
{
DelRef();
this->~Myshared_ptr();
_mptr = rhs._mptr;
AddRef();
}
return *this;
}
~Myshared_ptr()
{
DelRef();
if(GetRef() == 0)
{
delete _mptr;
}
_mptr = NULL;
}
T& operator*()
{
return *_mptr;
}
T* operator->()
{
return _mptr;
}
T* Getptr()const
{
return _mptr;
}
private:
void AddRef()
{
rm->addref(_mptr);
}
void DelRef()
{
rm->delref(_mptr);
}
int GetRef()
{
return rm->getref(_mptr);
}
T* _mptr;
static Refmanage* rm;
};
//静态成员变量类外初始化
template <typename T>
Refmanage* Myshared_ptr<T>::rm = Refmanage::GetSingle();
template<typename T>
class Myweak_ptr
{
public:
Myweak_ptr(T* ptr = NULL) :
_mptr(ptr)
{}
Myweak_ptr(const Myweak_ptr<T>& rhs)
:_mptr(rhs._mptr)
{}
Myweak_ptr<T>& operator=(const Myweak_ptr<T>& rhs)
{
if (this != &rhs)
{
_mptr = rhs._mptr;
}
return *this;
}
Myweak_ptr<T>& operator=(const Myshared_ptr<T>& rhs)
{
_mptr = rhs.Getptr();
return *this;
}
~Myweak_ptr()
{}
T* operator->()
{
return _mptr;
}
T& operator*()
{
return *_mptr;
}
private:
T* _mptr;
};
class B;
class A
{
public:
A()
{
cout<<"A::A()"<<endl;
}
~A()
{
cout<<"A::~A()"<<endl;
}
Myweak_ptr<B> pb;
};
class B
{
public:
B()
{
cout<<"B::B()"<<endl;
}
~B()
{
cout<<"B::~B()"<<endl;
}
Myweak_ptr<A> pa;
};
int main()
{
Myshared_ptr<A> a(new A());
Myshared_ptr<B> b(new B());
a->pb = b;
b->pa = a;
return 0;
}
上面代码的执行结果显示 没有内存泄露出现。
2. 问题
虽然 weak_ptr解决了强引用shared_ptr带来的 “循环引用-->内存泄露”的问题,但是使用weak_ptr的前提是 清楚代码中会出现shared_ptr相互引用的情况,但是并不总是容易看出,而且运行期的互相引用也不总是能够察觉。