【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++设计和编码中常见的问题,正确地使用它可以生 ...
随机推荐
- 题解 [SCOI2007]压缩
好题. 显然区间 dp,令 \(f_{l, r}\) 为 \([l, r]\) 之间的最短的长度.如果我们要压缩,那么就要考虑 M 与 R 的位置.由于我们大体是从左往右来转移的,所以显然我们只需要记 ...
- 未在本地计算机上注册“Microsoft.ACE.OLEDB.12.0”提供程序。(C# EXCEL导入demo)
1. 安装office包 https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=13255 2.需要在相应的IIS应用程序池启用 ...
- Linux 用户组管理
用户组 群组是大家都熟悉的东西,群组有群主,也就是创建者.群管理员有一定的管理权限,比如上传群文件.管理成员等权限:群成员也有一定的权限,比如下载群文件. 私有组 一般来说,每一个用户都有自己的一个初 ...
- C与Java中的动态数组
1. 引言 在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定.对于这种问题,用静态数组的办法很难解决. 动态数组,是相对于静态数组而言.静态数组的长度是预先定义 ...
- 浏览器控件webBrowser
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- gitlab中CI/CD过程中的坑
先上观点,azure的pipeline比gitlab ce版好用,gitlab收费版没有用过. 在.gitlab-ci.yml中的特殊字符处理: 解决方法: cmd="[$var1] &am ...
- .NET 6 + Hangfire 实现后台作业管理
一.环境: ASP.NET Core 6 + Hangfire + MySQL 二.新建ASP.NET Core空项目 项目名称:HangfireExample 框架:.NET 6.0 三.N ...
- 退役*CPCer的找实习总结
从2月底开始到今天,我终于拿到了第一个也是唯一一个offer(字节跳动).找实习的过程告一段落,所以想记录一下这段时间的经历. 最开始找$meopass$学长内推了小马智行,很快就接到了面试通知(再次 ...
- 浅谈flume
flume做日志收集的工具,将数据源导入到指定目标中.flume之间可以相互连接组件 source:如何从数据源中取数据,可以认为是两种主动source(主动取数据)和被动source(推给so ...
- Django 之RestFramework
1. 从request先说起 在Django原生的request里,请求的数据可以从request.GET或者request.POST里面取到. 需要注意的是,如果是POST请求,request.PO ...