【C++】关于智能指针的简单学习
智能指针
示例类:
class String {
private:
string m_value;
public:
String(string str) :m_value(str) {
cout << "构造" << m_value << "\n";
}
friend ostream& operator<<(ostream& os,const String& str) {
os << str.m_value;
return os;
}
~String() {
cout << "析构" << m_value << "\n";
}
};
智能指针是用于管理指针类型内存的一类对象类型,它指向的对象指针被回收的条件如下(对于auto_ptr和unique_ptr):
1.智能指针对象自然析构,即在一个语句块中智能指针为局部变量,离开这个语句块智能指针就被析构了,自然它指向的对象指针的那部分内存也要释放;
例:
{
auto_ptr<String> aptr(new String("hello"));
}
//离开语句块就回收
2.智能指针对象调用了reset,这个函数会直接把智能指针指向的对象清空掉或者指向了别的对象指针:
{
auto_ptr<String> uptr( new String("hello") );
uptr.reset(new String("hi")); //指向新的指针,此时就会释放掉原来那个指针
//或者uptr.reset();清空,不过下面的代码就会报异常了
cout << uptr.get() << ":" << *uptr.get() << "\n";
}
3.智能指针对象进行release,把指针的控制权交出去,可以由普通的指针去接release的返回值,然后普通指针再手动delete,这里的release指的不是释放内存,而是释放指针的控制权,当然如果不去手动delete的话,进程结束时该指针也不会调用析构函数。
例:
unique_ptr<String> uptr(new String("hello"));
cout << uptr.get() << ":" << *uptr.get() << "\n";
unique_ptr<String> uptr2(std::move(uptr));
cout << uptr2.get() << ":" << *uptr2.get() << "\n";
String* pstr = uptr2.release(); //放指针自由
delete pstr; //真正释放了指针
shared_ptr的回收思想与上述的auto_ptr、unique_ptr是类似的,当离开了语句块的时候shared_ptr就会被回收,但是不一样的地方在于shared_ptr引入了引用计数,只有当引用计数为0的时候才会进行对象指针的内存回收。
下面就看一下这三种指针:
auto_ptr
使用auto_ptr进行指针的管理,就可以不用手动去delete指针,例:
String* str = new String("string1");
auto_ptr<String> aptr(str);
当aptr被回收时,就会顺便把str回收了。但是这是有问题的,如果多个auto_ptr指向同一个对象指针,就会导致重复释放,例如:
String* str = new String("string1");
auto_ptr<String> aptr1(str);
auto_ptr<String> aptr2(str);
正确的做法应该是把指针的控制权交出去,而不是共享:
String* str = new String("string1");
auto_ptr<String> aptr1(str);
auto_ptr<String> aptr2(std::move(aptr1));
//又或者赋值运算符
auto_ptr<String> aptr2 = aptr1;
move或者赋值运算符都会把原先的auto_ptr中指向的对象指针交给新的auto_ptr,这样就不会出现上面两个智能指针指向同一个对象指针然后重复释放内存的问题,但是又有了一个新的问题,使用了赋值运算符将对象指针控制权转移了之后,可能会带来一个认知上的错误,就是原来那个智能指针可能还可以用,这时就会报异常了。
unique_ptr
unique_ptr对象之间无法使用赋值运算符去转移对象指针的控制权,不过使用move还是可以这样做,只能说使用的时候多长点心。unique_ptr和auto_ptr是很像的,我比较菜我感觉它们就是一样的,但是既然unique_ptr比较新,那就直接用这个unique_ptr。
下面附一个可能会踩的坑:
String GetString() {
return String("哼哼哼");
}
{
unique_ptr<String> uptr(&GetString());
cout << uptr.get() << ":" << *uptr.get() << "\n";//解引用时发生异常
}
这个函数返回的是一个临时的String对象,然后我对函数返回结果进行取地址,然后在语法上这是过得去的,但是要知道函数返回的临时对象只会存在一行,在下一行就会被回收,所以在下一行中虽然get能够获得地址,但是进行解引用的时候就会发生异常。
所以使用的时候要长点心,因为这个坑是适用于所有智能指针的,不能用智能指针去接函数返回的临时对象的地址。
shared_ptr
unique_ptr和auto_ptr不支持多个智能指针指向同一个对象,但是shared_ptr中是可以的,它使用了引用计数,只有当引用计数为0时才会释放shared_ptr指向的对象指针。
例:
String* pstr = new String("hello");
shared_ptr<String> sptr(pstr);
cout << sptr.use_count() << "\n";
sptr.reset(); //这里reset后就直接释放了对象指针
cout << sptr.use_count() << "\n";
下面测试多个shared_ptr指向同一个对象指针:
String* pstr = new String("hello");
shared_ptr<String> sptr(pstr);
cout << sptr.use_count() << "\n"; //1
shared_ptr<String> sptr2( pstr ); //无效,引用计数不会增加,相当于sptr和sptr2为管理上独立的两个shared_ptr
cout << sptr.use_count() << "\n";
shared_ptr<String> sptr3( sptr ); //引用计数+1
shared_ptr<String> sptr4( sptr ); //引用计数+1
cout << sptr.use_count() << "\n"; //3
sptr3.reset(); //sptr3释放了,但是,sptr的引用计数不为0,不会调用析构
sptr2.reset(); //sptr2释放了,并且因为引用计数为0,调用了析构
cout << sptr.use_count() << "\n"; //这里虽然引用计数为2,但是sptr指向的那个指针其实已经被回收了,只是现在引用计数为2,还没有被回收没发生异常
从这个例子中可以看出,如果要进行引用计数的话,多个shared_ptr必须是通过拷贝构造产生,不能够直接使用普通指针去构造,否则多个shared_ptr的引用计数就不是同一个引用计数,此时释放内存就会出现一些问题,因为即便引用计数不为0,它指向的指针也是有可能已经被释放掉了。
再看一个例子:
String* pstr = new String("hello");
shared_ptr<String> sptr1(pstr);
cout << sptr1.use_count() << "\n"; //1
shared_ptr<String> sptr2(sptr1); //引用计数+1
shared_ptr<String> sptr3( sptr1 ); //引用计数+1
cout << sptr1.use_count() << "\n"; //3
cout << sptr2.use_count() << "\n"; //3
sptr1.reset();
cout << sptr1.use_count() << "\n"; //0
cout << sptr2.use_count() << "\n"; //2
这个例子可以看出来,当多个shared_ptr在引用计数时,如果其中一个shared_ptr释放掉了,其实是把引用计数减一,然后自身退出了引用计数,而对于其他的shared_ptr没有产生影响。
weak_ptr
循环引用的问题:
假设两个类型内部都对对方有shared_ptr的聚合,就会造成循环引用。
class Soul;
class Human {
private:
shared_ptr<Soul> m_soul;
public:
Human() {
cout<< "构造了个人" << "\n" ;
}
void SetSoul(shared_ptr<Soul> soul) {
m_soul = soul;
}
~Human() {
cout << "析构了个人" << "\n";
}
};
class Soul {
private:
shared_ptr<Human> m_human;
public:
Soul() {
cout << "构造了个灵魂" << "\n";
}
void SetHuman(shared_ptr<Human> human) {
m_human = human;
}
~Soul() {
cout << "析构了个灵魂" << "\n";
}
};
shared_ptr<Human> human(new Human);
shared_ptr<Soul> soul(new Soul);
human->SetSoul(soul);
soul->SetHuman(human);
会发现这种情况下析构函数不调用,也就是内存不会释放,发生了内存泄漏,原因很简单,在外面引用计数为1,进行Set之后发生了shared_ptr拷贝构造,引用计数加1,就变成了2,当进程结束时,human要回收了,于是它要回收human指向的Human指针,但是发现这玩意在Soul里面有引用计数,然后它要回收soul,发现soul的引用计数在Human里面,于是谁都不释放。
解决方法也很简单,就是把类内部内聚的shared_ptr直接改为weak_ptr,weak_ptr可以接shared_ptr,并且不会增加shared_ptr的引用计数,并且weak_ptr没有重载*和->,所以无法对实际的对象做什么。
weak_ptr也有弱引用计数,但是这个似乎没啥用,因为对象指针回不回收是看shared_ptr的引用计数。
weak_ptr最重要的有两个函数,一个是lock,另一个是expired,因为weak_ptr不增加shared_ptr的引用次数,所以使用weak_ptr时,可能对象指针已经释放了,所以需要使用expired去检查指针的有效性(返回false为有效);如果需要访问对象的话,就需要使用lock去创建一个shared_ptr,shared_ptr的引用计数+1,当然shared_ptr只在当前的语句块中起作用,离开当前语句块后shared_ptr的引用计数-1。
看例子:
//给Human类增加一个Say的成员函数
void Say() {
cout << "我要献祭我的灵魂" << "\n";
}
//给Soul类增加一个listen的成员函数
void listen() {
if (!m_human.expired()) {
m_human.lock()->Say();
}
else {
cout << "人没了" << "\n";
}
}
shared_ptr<Human> human(new Human);
shared_ptr<Soul> soul(new Soul);
human->SetSoul(soul);
soul->SetHuman(human);
soul->listen();
human.reset();
soul->listen();
可以看到结果为:
构造了个人
构造了个灵魂
我要献祭我的灵魂
析构了个人
人没了
【C++】关于智能指针的简单学习的更多相关文章
- 【C++】智能指针auto_ptr简单的实现
//[C++]智能指针auto_ptr简单的实现 #include <iostream> using namespace std; template <class _Ty> c ...
- C++智能指针及其简单实现
本文将简要介绍智能指针shared_ptr和unique_ptr,并简单实现基于引用计数的智能指针. 使用智能指针的缘由 1. 考虑下边的简单代码: int main() { ); ; } 就如上边程 ...
- C++ 引用计数技术及智能指针的简单实现
一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧).最近花了点时间认真看了智能指针,特地来写这篇文章. 1.智能指针是什么 简单来说,智能指针是一个类,它对普 ...
- 【C++深入浅出】智能指针之auto_ptr学习
起: C++98标准加入auto_ptr,即智能指针,C++11加入shared_ptr和weak_ptr两种智能指针,先从auto_ptr的定义学习一下auto_ptr的用法. template& ...
- C++ 拷贝控制和资源管理,智能指针的简单实现
C++ 关于拷贝控制和资源管理部分的笔记,并且介绍了部分C++ 智能指针的概念,然后实现了一个基于引用计数的智能指针.关于C++智能指针部分,后面会有专门的研究. 通常,管理类外资源的类必须定义拷贝控 ...
- C++ 智能指针的简单实现
智能指针的用处:在c++中,使用普通指针容易造成堆内存的泄露问题,即程序员会忘记释放,以及二次释放,程序发生异常时内存泄漏等问题,而使用智能指针可以更好的管理堆内存.注意,在这里智能指针是一个类而非真 ...
- 智能指针shared_ptr使用学习
当需要shared_ptr实现向上向下转换时,可以使用 dynamic_pointer_cast 来进行转换 下面是例子: #include <memory> using namespac ...
- C++智能指针
引用计数技术及智能指针的简单实现 基础对象类 class Point { public: Point(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) { ...
- 27.C++- 智能指针
智能指针 在C++库中最重要的类模板之一 智能指针实际上是将指针封装在一个类里,通过对象来管理指针. STL中的智能指针auto_ptr 头文件: <memory> 生命周期结束时,自动摧 ...
- 智能指针auto_ptr & shared_ptr
转载:智能指针auto_ptr 很多人听说过标准auto_ptr智能指针机制,但并不是每个人都天天使用它.这真是个遗憾,因为auto_ptr优雅地解决了C++设计和编码中常见的问题,正确地使用它可以生 ...
随机推荐
- HTML5----响应式(自适应)网页设计(自动适应屏幕大小)
HTML5----响应式(自适应)网页设计(自动适应屏幕大小) 现在,很多项目都需要做响应式或者自适应的来适应我们不同屏幕尺寸的手机,电脑等设备,那么就需要我们在页面上下功夫,但移动端的布局不同于pc ...
- ROS服务通信(C++)
ROS服务通信C++ 效果图 结构总览 友情提醒 每一步编辑完,执行一下 Ctrl+Shift+B进行编译,及时排查错误 准备工作 第一步:创建工作空间 配置:roscpp rospy std_msg ...
- js手动触发页面元素点击事件,自定义点击事件模拟点击
// initEvent事件已经弃用1. 创建MouseEvents事件const clickEvent = document.createEvent('MouseEvents')2. 初始化点击事件 ...
- Anaconda 环境中安装OpenCV (cv2)
1.使用Anaconda 的对应环境,查看环境中的Python版本号 (1)使用Anaconda 查看存在的环境:conda info --env (2)激活环境:conda activate XXX ...
- pytho获取C函数返回值
python调用C语言接口 注:本文所有示例介绍基于linux平台 在底层开发中,一般是使用C或者C++,但是有时候为了开发效率或者在写测试脚本的时候,会经常使用到python,所以这就涉及到一个问题 ...
- .NET控制台程序秒变asp.net core站点
有个.NET控制台程序用来跑定时任务的,但是做好后需求发生变化,跑出的数据结果不能直接使用,数据需要转成json格式通过web接口来调用实现.这个控制台是个单体程序,没有封装,如果新建一个项目的话还得 ...
- KCL v0.4.5 发布 - 更好的编写便利性改进,稳定性,体验提升与多平台支持
简介 KCL 团队很高兴地宣布 KCL v0.4.5 版本现在已经可用!本次发布主要为 KCL 语言编写便利性和稳定性提升,错误信息改进以及更多平台包括 windows 版本支持以及更多下载方式支持. ...
- JavaSE总结(1)
Java发展历史.HelloWorld.常量.变量类型转换.运算符.方法(函数)1.jdk版本: jdk1.2---J2EE/J2SE/J2ME jdk1.5---JavaSE/JavaE ...
- .net core 3.1项目运行在Windows server 2012R2服务器上,Decimal类型小数点不见了,求解!32112.7958
.net core 3.1项目运行在Windows server 2012R2服务器上,Decimal类型小数点不见了,求解! string str = "1002910.8241" ...
- 如何使用源码编译安装Nginx服务器
安装 PCRE : 网站:http://pcre.org/ 下载: ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/ ftp://ftp.c ...