因为C++中没有自主的内存回收机制,new出来的内存一定要使用dalete释放,否则就会造成内存泄露,因此有了智能指针,智能指针的原理就是收到开辟,系统释放
智能指针共分为4种,即boost库中的auto_ptr、scoped_ptr、shared_ptr、weak_ptr。
一、c++11 auto_ptr-----所有权唯一,即就是只能有一个智能指针指向该内存块,如果有新的智能指针指向此内存,则要将旧的智能指针置为NULL
代码如下:
template<typename T>
class SmartPtr
{
public:
SmartPtr(T* ptr) :mptr(ptr){}
~SmartPtr()
{
delete mptr;
mptr = NULL;
}
SmartPtr(const SmartPtr<T>& rhs)
{
mptr = rhs.mptr;
rhs.Release();
}
SmartPtr<T>& operator=(const SmartPtr<T>& rhs)
{
if (this != &rhs)
{
delete mptr;
mptr = rhs.mptr;
rhs.Release();
}
return *this;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
void Release()const // const SmartPtr<T>* const this
{
(T*)mptr = NULL;
}
T* mptr;
};
int main()
{
SmartPtr<int> sp1 = new int;
SmartPtr<int> sp2 = sp1;//sp1.mptr = NULL;
**(1)***sp1 = 10;//出现问题
(2) int a = 10;
int p = &a;
int q = p;
p = 20;
return 0;*
}
#endif
我们知道,普通的指针可以如蓝色部分一样使用,但是在auto_ptr中,我们可以看到绿色部分(1)会出现错误,因为在执行完前两句后,sp1.mptr == NULL,再执行 sp1 =10,程序就会崩溃,因为访问了保留区的数据。
auto_ptr存在以下几个缺陷:
1>不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放;
2>不要使用两个auto_ptr指针指向同一个指针,具体原因上面解释过;
3>不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配;
4>不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用
二、scoped_ptr----所有权不唯一,释放权唯一
template<typename T>
class SmartPtr
{
public:
SmartPtr(T* ptr) :mptr(ptr), flag(true){}
SmartPtr(const SmartPtr<T>& rhs)
{
mptr = rhs.mptr;
flag = rhs.flag;
rhs.flag = false;
}
SmartPtr<T>& operator=(const SmartPtr<T>& rhs)
{
if (this != &rhs)
{
this->~SmartPtr();
mptr = rhs.mptr;
flag = rhs.flag;
rhs.flag = false;
}
return *this;
}
~SmartPtr()
{
if (flag)
{
delete mptr;
}
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
T* mptr;
mutable bool flag; //是否拥有释放权
};
// 智能指针提前销毁,其他智能指针失效
//SmartPtr<int> sp = sp1; //mptr:0x100 flag:true
void Test(SmartPtr<int> sp)
{}
int main()
{
SmartPtr<int> sp1(new int);//mptr:0x100 flag:false
Test(sp1);
*sp1 = 20;//
//SmartPtr<int> sp1(new int);
//SmartPtr<int> sp2(sp1);
//*sp1 = 20;
return 0;
}
可以看到scoped_ptr和auto_ptr一样,都有因为权限转移而引起的问题,为了防止权限转移,应将拷贝构造和赋值运算符的重载函数都放在私有成员下,表明了scoped_ptr无法用对象来生成另一个对象或给另一个对象赋值,提高了安全性,但是又存了在无法“++”、“–-”这些操作,所以这种形式叶并不是最完美的。所以又有了shared_ptr。
三、shared_ptr----带有引用计数的智能指针
下面是代码:
class Ref_Management
{
public:
static Ref_Management* getInstance()
{
return &rm;
}
private:
Ref_Management():cursize(0){}
Ref_Management(const Ref_Management&);
static Ref_Management rm;
public:
void addref(void* mptr)
{
if (mptr != NULL)
{
int index = find(mptr);
if (index < 0)
{
Node tmp(mptr, 1);
node[cursize++] = tmp;
//node[cursize].addr = mptr;
//node[cursize].ref = 1;
//cursize++;
}
else
{
node[index].ref++;
}
//std::vector<Node>::iterator fit = find(mptr);
//if (fit == vec.end())
//{
// Node node(mptr, 1);
// vec.push_back(node);
//}
//else
//{
// (*fit).ref++;
//}
}
}
void delref(void* mptr)
{
if (mptr != NULL)
{
int index = find(mptr);
if (index < 0)
{
throw std::exception("addr is not exsit!");
}
else
{
if (node[index].ref != 0)
{
node[index].ref--;
}
}
}
}
int getref(void* mptr)
{
int rt = -1;
if (mptr != NULL)
{
int index = find(mptr);
if (index >= 0)
{
rt = node[index].ref;
}
}
return rt;
}
private:
int find(void* mptr)
{
int rt = -1;
for (int i = 0; i < cursize; i++)
{
if (node[i].addr == mptr)
{
rt = i;
break;
}
}
return rt;
/*std::vector<Node>::iterator it = vec.begin();
for (it; it != vec.end(); it++)
{
if ((*it).addr == mptr)
break;
}
return it;*/
}
class Node
{
public:
Node(void* padd = NULL, int rf = 0) :addr(padd), ref(rf){}
public:
void* addr;
int ref;
};
Node node[10];
int cursize;//
};
Ref_Management Ref_Management::rm;
template<typename T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr = NULL) :mptr(ptr)
{
AddRef();
}
Shared_Ptr(const Shared_Ptr<T>& rhs) :mptr(rhs.mptr)
{
AddRef();
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
if (this != &rhs)
{
this->~Shared_Ptr();
mptr = rhs.mptr;
AddRef();
}
return *this;
}
~Shared_Ptr()
{
DelRef();
if (GetRef() == 0)
{
delete mptr;
}
mptr = NULL;
}
T* operator->()
{
return mptr;
}
T& operator*()
{
return *mptr;
}
private:
void AddRef()
{
prm->addref(mptr);
}
void DelRef()
{
prm->delref(mptr);
}
int GetRef()
{
return prm->getref(mptr);
}
T* mptr;
static Ref_Management* prm;
};
template<typename T>
Ref_Management* Shared_Ptr<T>::prm = Ref_Management::getInstance();
shared_ptr和以上两者的区别就是加入了引用计数,用于检测当前对象所管理的指针是否还被其他shared_ptr对象的指针使用,在析构函数时对其引用计数减一,判断是否为0,若为0,则释放这个指针和这个引用计数的空间。这个原理和String类的写时拷贝原理是一样的。
然而,智能指针也存在问题,下面就来看一下
class B;
class A
{
public:
A()
{
std::cout << "A()" << std::endl;
}
~A()
{
std::cout << "~A()" << std::endl;
}
public:
Shared_Ptr<B> spa;
};
class B
{
public:
B()
{
std::cout << "B()" << std::endl;
}
~B()
{
std::cout << "~B()" << std::endl;
}
public:
Shared_Ptr<A> spb;
};
int main()
{
Shared_Ptr<A> pa(new A());
Shared_Ptr<B> pb(new B());
pa->spa = pb;//存在问题
pb->spb = pa;//存在问题
return 0;
}
运行上面代码我们可以发现,只有A和B的构造,而没有析构,是什么原因造成了内存泄露
在两个shared_ptr指针相互指向时,引用计数变为2,因此在释放时调用析构引用计数会变为0,并不会被真正释放,因此有了weak_ptr。
template<typename T>
class Weak_ptr
{
public:
Weak_ptr(T* ptr = NULL) :mptr(ptr){}
Weak_ptr(const Weak_ptr<T>& rhs)
{
mptr = rhs.mptr;
}
Weak_ptr<T>& operator=(const Weak_ptr<T>& rhs)
{
if (this != &rhs)
{
mptr = rhs.mptr;
}
return *this;
}
Weak_ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
mptr = rhs.getPtr();
return *this;
}
~Weak_ptr()
{
}
T* operator->()
{
return mptr;
}
T& operator*()
{
return *mptr;
}
private:
T* mptr;
};
weak_ptr是解决两个shared_ptr相互指向的问题
1、不能单独使用,要和shared_ptr配合使用
2、不加引用计数