【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++设计和编码中常见的问题,正确地使用它可以生 ...
随机推荐
- 【ASP.NET Core】标记帮助器——替换元素名称
标记帮助器不仅可以给目标元素(标记)插入(或修改)属性,插入自定义的HTML内容,在某些需求中还可以替换原来标记的名称. 比如我们在使用 Blazor 时很熟悉的 Component 标记帮助器.在 ...
- vue-fullpage全屏插件使用
直入主题:vue项目中想做一个全屏翻滚的效果,vue-fullpage 就很不错 下面介绍vue-fullpage 的使用方法,这里封装成了vue的一个指令的形式来进行使用 1.安装vue-fullp ...
- 大道至简的架构设计思想之:封装(C系架构设计法,sishuok)
一起来看看大道至简的一些基本设计思想,首先我们来看一下什么是封装. 封装:也叫做信息隐藏,或者数据访问保护.放到程序上来讲,就是隐藏类的属性,还有实现细节,仅对外公开一些接口.那么外部,就只能通过这个 ...
- day09-MyBatis缓存
MyBatis缓存 mybatis – MyBatis 3 | cache MyBatis 一级缓存全详解(一) MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制. 为了 ...
- MyCat2 分表分库
1.添加数据库.存储数据源 我们在读写分离那边已经生成过,不需要在执行,如果没有执行过,执行下面注解,我们这边重新创建一个数据库db1 /*+ mycat:createDatasource{ &quo ...
- 学习笔记3:Android Studio 配置NDK编译c++代码
NDK编译c++代码有两个方式: 1 ndk-build.cmd + Android.mk + Application.mk 编译, 可单独用ndk编译, 不使用IDE,使用Android需要配置b ...
- 使用vue+iview创建自己的对话框组件
通过对别的案例反复研究,终于总结出自己对于使用vue+iview创建组件的步骤: 第一步:编辑新建对话框组件(子组件) <template> <div> <!-- mod ...
- JS 开始时间/结束时间和当前时间进行比较
项目需求:到截止日期一些功能不可以再使用,那么需要判断当前时间与截止时间进行比较,记录一下吧 注意: 1.橙色字体的代码换成你自己的变量 2. .valueOf()其实就是将中国时间转为时间戳 3.截 ...
- linux top 指令各列含义
Linux 的 top 指令用于显示机器上正在运行的进程的信息.下面是 top 指令各列的含义: PID:进程 ID,用于标识进程. USER:进程所有者的用户名. PR:进程优先级. NI:进程的& ...
- webpack5的基本用法
webpack的基本使用 webpack 本身功能有限: 开发模式: 仅能编译JS中的ES Module 语法 生产模式: 能编译ES Module 语法, 还能压缩JS代码 添加实例文件 npm i ...