std::auto_ptr指针在C++11标准中被移除了,可以使用unique_ptr来代替,功能上是相同的,unique_ptr相比较auto_ptr而言,提升了安全性(没有浅拷贝),增加了特性(delete析构)和对数组的支持。
这个类模版提供了有限的垃圾回收机制,通过将一个指针保存在auto_ptr对象中,当auto_ptr对象析构时,这个对象所保存的指针也会被析构掉。
auto_ptr对象拥有其内部指针的所有权。这意味着auto_ptr对其内部指针的释放负责,既当自身被释放时,会在析构函数中自动的调用delete,从而释放内部指针的内存。
解释:
到这里,我们来看一下auto_ptr的提供的接口和使用方法:
其中构造只的说一下:
解释:auto_ptr的构造的参数可以是一个指针,或者是另外一个auto_ptr对象。
#include
#include
int main(int argc, char const *argv[]){
// 通过指针进行构造
std::auto_ptr aptr1(new int(3));
std::printf("aptr1 %p : %d\r\n", aptr1.get(), *aptr1);
// 这样会编译出错,因为auto_ptr的构造有关键字explicit
// explicit 关键字表示调用构造函数是不能使用隐式赋值,而必须是显示调用
// std::auto_ptr aptr2 = new int(3);
// 可以用其他的auto_ptr指针进行初始化
std::auto_ptr aptr2(new int(44));
std::printf("aptr2 %p : %d\r\n", aptr2.get(), *aptr2);
std::printf("===========================\n");
// aptr2 会被释放
aptr1 = aptr2;
std::printf("aptr1 %p : %d\r\n", aptr1.get(), *aptr1);
// 内存访问出错,直接0xc05,因为aptr2已经释放了其所有权
// std::printf("aptr2 %p : %d\r\n", aptr2.get(), *aptr2);
return 0;
}
#include
#include
int main(int argc, char const *argv[]){
int * pNew = new int(3);
{
std::printf("pNew:%d\n", *pNew);
//当释放aptr时,pNew 也会被释放了(注意,要出作用域才释放)
std::auto_ptr aptr(pNew);
std::printf("aptr:%d\n", *aptr);
}
std::printf("pNew:%d\n", *pNew);
return 0;
}
在这里可以使用前调用release,从而放弃其内部指针的使用权,但是同样这么做违背了智能指针的初衷。
#include
#include
int main(int argc, char const *argv[]){
int * pNew = new int(3);
{
std::printf("pNew:%d\n", *pNew);
std::auto_ptr aptr(pNew);
// aptr 放弃其内部指针的使用权
int* p = aptr.release();
// std::printf("aptr:%d\n", *aptr);
}
std::printf("pNew:%d\n", *pNew);
return 0;
}
#include
#include
int main(int argc, char const *argv[]){
int * pNew = new int(3);
{
std::printf("point:%p, pNew:%d\n", pNew, *pNew);
std::auto_ptr aptr(pNew);
std::printf("point:%p, aptr:%d\n", aptr.get(), *aptr);
// aptr 重新分配指针的所有权, pNew的内容被清除
aptr.reset(new int(555));
std::printf("====================\n");
std::printf("point:%p, aptr:%d\n", aptr.get(), *aptr);
std::printf("point:%p, pNew:%d\n", pNew, *pNew);
}
std::printf("====================\n");
std::printf("point:%p, pNew:%d\n", pNew, *pNew);
return 0;
}
#include
#include
int main(int argc, char const *argv[]){
{
std::auto_ptr p1;
std::auto_ptr p2;
p1 = std::auto_ptr(new int(33));
std::printf("point:%p, pNew:%d\n", p1.get(), *p1);
*p1 = 4;
std::printf("point:%p, pNew:%d\n", p1.get(), *p1);
// p1 会被释放
p2 = p1;
// std::printf("point:%p, pNew:%d\n", p1, *p1);
std::printf("point:%p, pNew:%d\n", p2, *p2);
}
return 0;
}
为什么C11标准会不让使用auto_ptr,原因是其使用有问题。
作为参数传递会存在问题
#include
#include
void func_test(std::auto_ptr p){
std::printf("point:%p, p:%d \n", p.get(), *p);
}
int main(int argc, char const *argv[]){
std::auto_ptr p1 = std::auto_ptr(new int(44));
func_test(p1);
// 这里的调用会出错,因为拷贝构造函数的存在,p1实际上已经释放了其内部指针的所有权了
// std::printf("point:%p, p1:%d \n", p1.get(), *p1);
return 0;
}
不能使用vector数组
#include
#include
#include
int main(int argc, char const *argv[]){
std::vector> ary;
std::auto_ptr p(new int(33));
// 用不了
ary.push_back(p);
return 0;
}
前面我们讲解了auto_ptr的使用及为什么会被C++11标准抛弃,接下来,我们学习unique_ptr的使用
unique_ptr提供一下操作:
从根源杜绝了auto_ptr作为参数传递的写法
#include
#include
int main(int argc, char const *argv[]){
// 这样构造是可以的
std::unique_ptr p1(new int(3));
// 空构造也是可以的
std::unique_ptr p2;
// 下面三种写法会报错
// std::unique_ptr p3 = p1; // 需要拷贝构造
// std::unique_ptr p4(p1); // 需要拷贝构造
// p2 = p1; // 需要=运算符重载
return 0;
reset的用法和auto_ptr是一致的
#include
#include
int main(int argc, char const *argv[]){
int *pNew = new int(4);
int *p = new int(1);
{
std::printf("pNew:%d, p:%d\n", *pNew, *p);
// pNew 会被释放
std::unique_ptr uptr(pNew);
uptr.reset(p);
// *p = 444;
std::printf("pNew:%d, p:%d, uptr:%d \n", *pNew, *p, *uptr);
// uptr 出作用域会被释放,p也会被置空,但p指针没有被释放
}
//指针没有被释放
*p = 99;
std::printf("pNew:%d, p:%d\n", *pNew, *p);
return 0;
}
release与reset一样,也不会释放原来的内部指针,只是简单的将自身置空。
#include
#include
int main(int argc, char const *argv[]){
int *pNew = new int(7);
int *p = NULL;
{
std::unique_ptr uptr(pNew);
std::printf("pNew point:%p, v:%d\n", pNew, *pNew);
std::printf("uptr point:%p, v:%d\n", uptr.get(), *uptr);
p = uptr.release();
std::printf("pNew point:%p, v:%d\n", pNew, *pNew);
// 报错 uptr 被置空
// std::printf("pNew point:%p, v:%d\n", uptr.get(), *uptr);
std::printf("p point:%p, v:%d\n", p, *p);
}
std::printf("pNew point:%p, v:%d\n", pNew, *pNew);
std::printf("p point:%p, v:%d\n", p, *p);
return 0;
}
但是多了个move的用法
因为unique_ptr不能将自身对象内部指针直接赋值给其他unique_ptr,所以这里可以使用std::move()函数,让unique_ptr交出其内部指针的所有权,而自身置空,内部指针不会释放。
#include
#include
int main(int argc, char const *argv[]){
int *p = new int(99);
std::unique_ptr uptr(p);
std::printf("uptr point:%p, v:%d\n", uptr.get(), *uptr);
std::unique_ptr uptr2 = std::move(uptr);
std::printf("p point:%p, v:%d\n", p, *p);
// 报错 uptr 被置空
// std::printf("uptr point:%p, v:%d\n", uptr.get(), *uptr);
std::printf("uptr2 point:%p, v:%d\n", uptr2.get(), *uptr2);
return 0;
}
可以采用move的方式来使用数据。
直接使用仍然会报错:
#include
#include
#include
int main(int argc, char const *argv[]){
std::vector> Ary;
std::unique_ptr p(new int(3));
// 报错,不能直接赋值
// Ary.push_back(p);
Ary.push_back(std::move(p));
// 报错 p已被置空
// std::printf("p:%d\n", *p);
return 0;
}
shared_ptr是带引用计数的智能指针
1、构造
其初始化多了一个写法:std::make_shared
#include
#include
int main(int argc, char const *argv[]){
int *p = new int(11);
std::shared_ptr sptr1(p);
std::shared_ptr sptr2(new int(5));
std::shared_ptr sptr3 = sptr2;
std::shared_ptr sptr4 = std::make_shared(4);
std::printf("sptr1 point:%p, v:%d\n", sptr1.get(), *sptr1);
std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
std::printf("sptr3 point:%p, v:%d\n", sptr3.get(), *sptr3);
std::printf("sptr4 point:%p, v:%d\n", sptr4.get(), *sptr4);
return 0;
}
这里显然可以看到有引用计数的存在
通过修改上面例子种的是sptr3的作用域之后,shared_ptr对应的引用计数的值减少了。
#include
#include
int main(int argc, char const *argv[]){
std::shared_ptr sptr2(new int(5));
{
std::shared_ptr sptr3 = sptr2;
*sptr3 = 9;
std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
std::printf("sptr3 point:%p, v:%d\n", sptr3.get(), *sptr3);
}
std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
return 0;
}
注意事项
#include
#include
int main(int argc, char const *argv[]){
int *p = new int(44);
{
std::shared_ptr sptr1(p);
{
// 运行报错,指针p 会被释放
std::shared_ptr sptr2(p);
}
// 出作用域,会释放指针p,会导致p被重复释放
}
return 0;
}
显然出了最里面的作用域之后,sptr2对象就已经释放了,此时,对于sptr2来说,p的引用计数为0,所以p被释放,但是实际上sptr1还存在,所以再释放sptr1时,就会报错。
#include
#include
int main(int argc, char const *argv[]){
{
std::shared_ptr sptr1(new int(5));
{
std::shared_ptr sptr2(sptr1);
*sptr2 = 9;
std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
}
std::printf("sptr2 point:%p, v:%d\n", sptr1.get(), *sptr1);
}
return 0;
}
share_ptr最多的问题时存在循环引用的问题:
如果两个类的原始指针的循环使用,那么会出现重复释放的问题。
这里,delete pSon会出现循环调用父子类的析构函数,问题很大。
#include
#include
class CSon;
class CPerson;
class CPerson{
public:
CPerson(){}
void Set(CSon *pSon){
m_pson = pSon;
}
~CPerson(){
if(m_pson != nullptr){
delete m_pson;
m_pson = nullptr;
}
}
public:
CSon *m_pson;
};
class CSon{
public:
CSon(){}
void Set(CPerson *pParent){
m_pParent = pParent;
}
~CSon(){
if(m_pParent != nullptr){
delete m_pParent;
m_pParent = nullptr;
}
}
public:
CPerson *m_pParent;
};
int main(int argc, char const *argv[]){
CPerson *pPer = new CPerson();
CSon *pSon = new CSon();
pPer->Set(pSon);
pSon->Set(pPer);
// delete pSon;
return 0;
}
因此,这里考虑使用引用计数的shared_ptr来实现。
#include
#include
class CSon;
class CPerson;
class CPerson{
public:
CPerson(){}
void Set(std::shared_ptr pSon){
m_pson = pSon;
}
~CPerson(){}
public:
std::shared_ptr m_pson;
};
class CSon{
public:
CSon(){}
void Set(std::shared_ptr pParent){
m_pParent = pParent;
}
~CSon(){}
public:
std::shared_ptr m_pParent;
};
int main(int argc, char const *argv[]){
// CPerson *pPer = new CPerson();
// CSon *pSon = new CSon();
// 循环的引用,会出现析构异常
// weak_ptr 弱指针
{
// std::shared_ptr shared_parent(pPer);
// std::shared_ptr shared_son(pSon);
std::shared_ptr shared_parent(new CPerson());
std::shared_ptr shared_son(new CSon());
shared_parent->Set(shared_son);
shared_son->Set(shared_parent);
std::printf("pPer: %d\n", shared_parent.use_count());
std::printf("pSon: %d\n", shared_son.use_count());
}
return 0;
}
这里在出作用域后发现,实际上两个对象均为被销毁
最后两者的引用计数均为1,原因是除了块作用域之后,两个shared_parent和shared_son均会析构,在这两个智能指针的内部,均会先去判断对应的内部指针引用次数-1是否为0,显然,这里相互引用的情况下,引用次数初值为2,减1后值为1,所以两个指针均不会被释放。
这里,其实只需要一个释放了,另外一个也能跟着释放,可以采用弱指针,即认为的迫使其中一个引用计数为1,从而打破闭环。
这里只需要将上例子中的任意一个强指针改为弱指针即可。
#include
#include
class CSon;
class CPerson;
class CPerson{
public:
CPerson(){}
void Set(std::shared_ptr pSon){
m_pson = pSon;
}
~CPerson(){}
public:
std::shared_ptr m_pson;
};
class CSon{
public:
CSon(){}
void Set(std::shared_ptr pParent){
m_pParent = pParent;
}
~CSon(){}
public:
std::weak_ptr m_pParent;
};
int main(int argc, char const *argv[]){
// CPerson *pPer = new CPerson();
// CSon *pSon = new CSon();
// 循环的引用,会出现析构异常
// weak_ptr 弱指针
{
// std::shared_ptr shared_parent(pPer);
// std::shared_ptr shared_son(pSon);
std::shared_ptr shared_parent(new CPerson());
std::shared_ptr shared_son(new CSon());
shared_parent->Set(shared_son);
shared_son->Set(shared_parent);
std::printf("pPer: %d\n", shared_parent.use_count());
std::printf("pSon: %d\n", shared_son.use_count());
}
return 0;
}
最后的结果,此时,两个内部指针均会得到释放。
前4个字节是虚表指针
中间两个4字节分别是内部对象指针计数器和自身的计数其
最后4字节是内部对象指针。
到这里就shared_ptr与weak_ptr的代码分析的差不多了
最后说一下计数器增减的规则
初始化及增加的情形
减少的情形
那么就可以自己来模拟强弱指针,并修改成模版。
思考
强指针直接构造(拿原始指针构造)时