C++学习2
创始人
2025-05-29 04:07:41

std::auto_ptr

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对象之间发生赋值操作时,内部指针被拥有的所有权会发生转移,这意味着这个赋值的右者对象会丧失该所有权,不在指向这个内部指针(其会被设置成null指针)。

到这里,我们来看一下auto_ptr的提供的接口和使用方法:

  • constructor
  • destructor
  • get
  • operator*
  • operator->
  • operator=
  • release
  • reset
  • conversion operators

其中构造只的说一下:

  • std::auto_ptr::auto_ptr

解释:auto_ptr的构造的参数可以是一个指针,或者是另外一个auto_ptr对象。

  • 当一个新的auto_ptr获取了内部指针的所有权后,之前的拥有者会释放其所有权
  1. 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;
}
  1. auto_ptr析构及资源的自动释放
#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;
}
  • 这里显然,当出了块作用域之后,aptr对象会自动调用析构,然后在析构中会自动的delete其内部指针,也就是出了这个作用域后,其内部指针就被释放了。
  • 当然上面这种写法是不推荐的,因为我们这里本质上就是希望不去管理指针的释放工作,上面的写法就又需要成员自己操心指针的问题,也就是使用智能指针要避免出现指针的直接使用!

在这里可以使用前调用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;
}
  1. 分配新的指针所有权
  • 可以调用reset来重新分配指针的所有权,reset中会先释放原来的内部指针的内存,然后分配新的内部指针。
#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;
}
  1. = 运算符的使用
#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;
}

auto_ptr存在的问题

为什么C11标准会不让使用auto_ptr,原因是其使用有问题。
作为参数传递会存在问题

  • 因为有拷贝构造函数和赋值的情况下,会释放原有的对象的内部指针,所有当有函数使用的是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;
}

unique_ptr

前面我们讲解了auto_ptr的使用及为什么会被C++11标准抛弃,接下来,我们学习unique_ptr的使用
unique_ptr提供一下操作:

  • (constructor)
  • (destructor)
  • operator=
  • get
  • get_deleter
  • operator bool
  • release
  • reset
  • swap
  • operator*
  • operator->
  • operator[]
  1. 析构函数

从根源杜绝了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;
  1. reset

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;
}
  1. release

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;
}
  1. move

但是多了个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;
}
  1. 数组

可以采用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;
}

shareed_ptr与weak_ptr

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;
}

注意事项

  • 如果用同一个指针去初始化两个shared_ptr时,则引用计数仍然会出错:
#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的代码分析的差不多了
最后说一下计数器增减的规则
初始化及增加的情形

  • 当创建一个新的shared_ptr时,内部对象计数器和自身的计数器均置1
  • 当将另外一个shared_ptr赋值给新的shared_ptr时,内部对象计数器+1,自身计数器不变。
  • 当将另外一个shared_ptr赋值给新的weak_ptr时,内部对象计数器不变,自身计数器+1.
  • 当从weak_ptr获取一个shared_ptr时,内部对象计数器+1,自身计数器不变。

减少的情形

  • 当一个shared_ptr析构时,内部对象计数器-1,当内部对象计数器减为0时,则释放内部对象,并将自身计数器-1
  • 当一个weak_ptr析构时,自身计数器-1,当自身计数器减为0时,则释放自身_Ref_count* 对象

那么就可以自己来模拟强弱指针,并修改成模版。
思考

  • 强指针构造,析构,=赋值,拷贝构造等情况下,计数器的变化
  • 弱指针构造,析构,=赋值,拷贝构造等情况下,计数器的变化
  • 弱指针提升为强指针时,计数器的变化

强指针直接构造(拿原始指针构造)时

  • 初始化_Ty *_Ptr
  • 创建_Ref_count对象
  • _Ref_count_base对象构造时,分别为_Uses = 1 并且 _Weaks = 1

相关内容

热门资讯

第一部分——简单句——第一章—... 谓语动词的物种变化 (二)情态   情态不变动词 can be/must...
网商银行40亿永续债获批!银行... 本文来源:时代周报 作者:黄宇昆时隔5年,浙江网商银行再次获批发债“补血”。近日,浙江金融监管局发布...
甜粽子和咸粽子都没人吃了? 在端午节的传统美食中,甜粽子和咸粽子曾是人们餐桌上的常客。然而,如今却出现了一种令人惊讶的现象,那就...
超4100只个股下跌 超410... 2025.05.30本文字数:424,阅读时长大约1分钟作者 |一财资讯截至午间收盘,沪指跌0.31...
「午盘」A股早盘弱势震荡收跌,... A股三大股指5月30日集体低开。早盘两市探底回升,三大股指跌势明显,个股呈现普跌态势。从盘面上看,可...
English Learnin... English Learning - L2 语音作业打卡 复习对比 [ɔ:] [ɒ] Day22 2...
Java设计模式 02-工厂模... 工厂模式 一、简单(静态)工厂模式 1、看一个具体的需求 看一个披萨的项目:要便于披萨...
嘉应制药信披违规突遭立案,养天... 5月28日晚,广东嘉应制药股份有限公司(以下简称“嘉应制药”)发布公告,公司收到中国证券监督管理委员...
河北保定蠡县县委书记陈伟已跨市... 澎湃新闻记者从相关方面独家获悉,原任河北保定蠡县县委书记的陈伟近日已跨市调任廊坊三河市委书记。 陈...
4月,全国发行新增债券2534... 2025年4月地方政府债券发行和债务余额情况 一、全国地方政府债券发行情况 (一)当月发行情况。 2...
JFX-A型精子质量分析仪空气... JFX-A型精子质量分析仪空气减震器:为精准医疗保驾护航行 在现代医疗技术中,精子质量分析仪是评估男...
“王健林卖万达广场”话题连续4... "先定个小目标"的王健林,如今却像清仓特卖般疯狂甩卖手里近1/5的万达广场,背后暗藏什么玄机? 短...
AI概念股早盘走弱,人工智能相... AI概念股早盘走弱,中科星图跌超8%,芯原股份、寒武纪-U、光环新网跌超3%。 受盘面影响,人工智能...
原创 稀... 近期,据美国《纽约时报》援引匿名知情人士的话报道称,美国商务部已暂停部分允许美国公司向中国商用飞机有...
一线城市豪宅市场火爆:500亿... “日光盘”接连上演,上海楼市持续高温。 5月27日,上海共有7个楼盘集中入市,其中3个项目实现“日光...
信泰人寿合规之殇:国资入局能否... 信泰人寿从民企转为国企,过程布满荆棘。自2007年成立以来,经历了股东内斗、偿付能力危机、高管贪腐、...
傲农生物“脱险”后,何时恢复盈... 得益于2024年财报的向好表现,福建傲农生物科技集团股份有限公司(简称“傲农生物”)近日被撤销退市风...
Java:分布式RPC框架Ap... 目录一、软件架构的演进过程【了解】二、Dubbo概述【了解】1、Dubbo简介2、Dubb架构三、服...
长肥网络与TCP的长肥管道 本文目录1、简化的理解网络模型2、时延带宽积的定义3、长肥网络与TCP长肥管道的定义4、TCP长肥管...
从NLP视角看电视剧《狂飙》,... 文章目录1、背景2、数据获取3、文本分析与可视化3.1 短评数据预处理3.2 词云图可视化3.3 t...
下一个万亿级蓝海市场,数字能源... 随着全球碳中和共识的形成、能源转型的推进,数字能源作为新一代能源技术的重要组成部分,成为各国政府和企...
【CSS】P9 选择器优先级 选择器优先级抛出一个问题选择器优先级权重!important外部样式与内部样式冲突 抛出一个问题 ...
孙悟空为何成了完美男友? 孙悟空成为完美男友,实乃其独特特质所致。他神通广大,能在女友遇到危险时瞬间现身,如那盖世英雄,给予最...
深市同标的规模最大的证券ETF... 5月30日,A股早盘震荡走低,券商板块回调。相关ETF中,证券ETF(159841)截至发稿跌0.7...
中建投信托地产风险化解仍需时日... 中建投信托仍然被“地产旧伤"拖累。文/每日财报 汇水在信托行业深度转型的2024年,年报数据清晰反...
RTP载荷H265(实战细节) H264与H265协议详解RTP载荷H264(实战细节) RTP载荷H2...
【2023.3.8】数据结构复... 【2023.3.8】数据结构复习笔记 文章目录【2023.3.8】数据结构复习笔记序言一、绪论二、线...
赛力斯应邀出席东盟重要经济论坛... 近年来,伴随着中国汽车产业的全面国际化,中国车企的全球影响力日益提升,就在最近赛力斯被应邀出席东盟-...
聚集更有国际化潜力研发管线 君... 5月29日,上海君实生物医药科技股份有限公司(简称“君实生物”)发布关于部分募投项目子项目变更及金额...
面经-2023-哲库Zeku-... 专栏推荐:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 专栏首页:2023 数字IC...