C++ shared_ptrweak_ptr的简单介绍和仿写
文章目录
- shared_ptr
- 一、shared_ptr的简单介绍
- 二、shared_ptr的使用
- 1.函数介绍
- 2.使用
- 三、shared_ptr对象创建方法的讨论
- 1. 有两种常见的创建的方法:
- 2. 有关make_shared函数
- weak_ptr
- 一、weak_ptr的简单介绍
- 二、weak_ptr的使用
- 1.成员函数介绍
- 2. 使用
- 关于shared_ptr造成的循环引用以及解决
- 1.循环引用
- 2.解决方案
- shared_ptr和weak_ptr的仿写
- 1. 代码展示:
- 2. 使用
- 总结
shared_ptr
一、shared_ptr的简单介绍
- shared_ptr是一个引用计数的智能指针,允许多个指针指向同一个对象。用引用计数来管理对象的资源。当用一个shared_ptr指针去初始化另一个shared_ptr指针的时候,引用计数会加一。当某一个shared_ptr生命周期结束,引用计数会减一。当引用计数为0 的时候,shared_ptr所指向的资源会被释放,引用计数器也会被释放。
- 结构
二、shared_ptr的使用
1.函数介绍
①unique()函数
:用来判断shared_ptr是否是资源的唯一拥有者。
②use_count()函数
:检查引用计数。
③get()函数
:得到shared_ptr所指向的堆区资源。
④reset()函数
:重置shared_ptr.参数为一个原生指针
⑤swap()函数
:交换两个shared_ptr。
主要函数如下图:
2.使用
shared_ptr的构造函数是不能进行隐式转换的。使用了explicit关键字。
shared_ptr<vector<int>> vc = new vector<int>({1,2,3}); //编译错误
shared_ptr<vector<int>> vc (new vector<int>({1,2,3})); //正确
class Object
{private:int value;
public:Object(int val = 0) :value(val) { cout << "construct Object" << endl; }~Object() { cout << "destruct Object" << endl; }int Value() { return value; }const int Value() const { return value; }
};
int main()
{shared_ptr<Object> obj(new Object(10));cout << obj->Value() << (*obj).Value() << endl; //10 10shared_ptr<Object> obj2(new Object(20));cout << obj2->Value() << endl; //20obj2 = obj;cout << obj2->Value() << endl; //10shared_ptr<Object> obj3(obj2);cout << obj3->Value() << endl; //10cout << obj.use_count() << endl; //3obj3.reset(new Object(30)); cout << obj3->Value() << " obj3.use_count():" << obj3.use_count() << " obj.use_count:" << obj.use_count() << endl;// 30 1 2obj3.swap(obj2);cout << obj2->Value() << " obj2.use_count():" << obj2.use_count() << " obj3.use_count:" << obj3.use_count() << endl;// 30 1 2return 0;
}
结果:
三、shared_ptr对象创建方法的讨论
1. 有两种常见的创建的方法:
(1)调用shared_ptr的构造函数创建
(2)调用make_shared函数创建。这个函数也是定义在memory头文件中。
shared_ptr<Object> obj1(new Object(10));
shared_ptr<Object> obj2 = make_shared<Object>(100);
shared_ptr<string> s = make_shared<string>("aaaa");
关于构造函数的创建就不必多说了,shared_ptr是非侵入式的,当传入的指针不为空时,在构造函数中会执行在堆区创建一块空间的操作,这块空间就是引用计数的空间,所以引用计数的空间和shared_ptr指向的资源并不在一块。所以这就发生了两次内存的分配。但是内存的分配和回收是C++最慢的单次操作了
。
而make_shared函数的创建规则是:合并这两块空间为一块,即可以同时为计数器和原生内存分配空间。一次就把他们创建完成,并且在一块释放。即把二者的内存视为一个整体进行管理
。
2. 有关make_shared函数
1. make_shared的优点:
(1)减少了单次分配内存的次数
(2)增大cache的局部性:计数器课原生内存紧邻,所以这就减少了一般的cachemiss。
(3)异常的安全性比另一种方案更优。
看下面的代码:如果采用构造函数创建的方法。这个dosomething函数的参数的构建顺序是,先new Object{1024}这个空间,然后看第二个参数是否抛出异常,如果抛出异常就执行return 0。所以这就导致构建的对象得不到释放。(因为智能指针还没有构建,只是开辟了原生内存)。但是如果使用make_shared函数来进行,就比较好了。开辟这两块空间是一起进行的,如果抛出异常就都不创建。
2. make_shared 的缺点:
可能会存在对象析构了,但空间没有释放的问题。
如下图所示,当在链表中使用智能指针的时候,因为开辟的是连续的空间,只有mcnt_s == 0 && mcnt_w == 0&& mptr == nullptr才能释放这一大块空间,但是这种情况下,Object对象已经析构掉了(执行了objlist.pop_front()),但是wp还在生存期,所以造成了这一大块空间要一直存在,直到weak_ptr的对象wp生存期结束之后,mcnt_w的值为0,才会释放这一大块空间。
weak_ptr
一、weak_ptr的简单介绍
weak_ptr是配合shared_ptr使用的一个智能指针。它指向shared_ptr管理的对象,但是不影响对象的生命周期,不会改变shared_ptr的引用计数。
无论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。
二、weak_ptr的使用
1.成员函数介绍
(1)lock()函数
:获得一个shared_ptr。
(2)expired()
:判断所指对象是否已经被销毁。
(3)reset()
:重置
(4)use_count()
:当前的引用计数值。
2. 使用
最常用的两个方法是lock()
和expired()
;
class Object
{private:int value;
public:Object(int val = 0) :value(val) { cout << "construct Object" << endl; }~Object() { cout << "destruct Object" << endl; }int Value() { return value; }const int Value() const { return value; }
};
int main()
{shared_ptr<Object> objs1(new Object(10));shared_ptr<Object> objs2(new Object(20));weak_ptr<Object> objw(objs1);cout << objw.use_count() << endl; //1shared_ptr<Object> objs3= objw.lock(); //lock()cout << objs1.use_count() << endl; //2objs1.reset();if (objw.expired())cout << "1过期" << endl;objs3.reset();if (objw.expired())cout << "2过期" << endl;return 0;
}
结果:
首先,进入主函数之后,构建了两个对象,然后objw指向objs1,之后用objs3.lock() (用objs1构建的一个智能指针)来构建objs3,所以,objs1的引用计数加一变成2,然后objs1重置为空,引用计数减一,这时候objw指向的对象还没有被析构,所以不会打印“1过期”,当重置objs3位空时,引用计数变为0,对象被析构(打印destruct Object),所以objw指向的对象被析构,所以打印“2过期”。最后打印的“destrcut Object“是objs2生命周期结束的时候调用析构函数,判断引用计数为1,减一之后为0,把对象析构掉。
关于shared_ptr造成的循环引用以及解决
1.循环引用
代码如下:
#include<iostream>
using namespace std;class child;
class parent
{public:shared_ptr<child> c;parent(){ cout << "parent construct" << endl; }void fun() { cout << "parent" << endl; }~parent() { cout << "~parent" << endl; }
};
class child
{public:shared_ptr<parent> p;child() { cout << "child construct" << endl; }void fun() { cout << "chile" << endl; }~child() { cout << "~child" << endl; }
};
int main()
{shared_ptr<parent> par(new parent());shared_ptr<child> pch(new child());par->c = pch;pch->p = par;par->fun(); pch->fun();return 0;
}
结果
:
由上面的结果可以看到,只调用了构造函数,对象一直都没有析构。这就造成了内存的泄露。这是因为Parent和Child对象内部,具有各自指向对方的 shared_ptr,加上 parent和child这两个shared_ptr,说明每个对象的引用计数都是2。当程序退出时,即使parent和child被销毁,也仅仅是导致引用计数变为了1,因此并未销毁Parent和Child对象。
2.解决方案
- 定义对象使用强智能指针,引用对象使用弱智能指针。
把parent和child类里的智能指针换成weak_ptr可以解决。weak_ptr不会增加shared_ptr的引用计数。所以当执行下面两行代码时不会使得引用计数增加,所以,程序结束的时候,par和pch调用各自的析构函数,使得引用计数减一之后变为0,这时候就可以析构各自指向的对象了。
系统只管理栈区的对象,当函数执行结束的时候,系统自动调用par和pch的析构函数。堆区空间系统不管理,要程序员自己析构。
par->c = pch;
pch->p = par;
修改代码:
weak_ptr<child> c;
weak_ptr<parent> p;
修改之后的执行结果如下:
shared_ptr和weak_ptr的仿写
1. 代码展示:
namespace jqw
{template<class T>class RefCnt{private:T* mPtr;std::atomic<int> mCnt_s;std::atomic<int> mCnt_w;public:RefCnt(T* ptr = nullptr) :mPtr(ptr){if (mPtr != nullptr){mCnt_s = 1;mCnt_w = 0;}}~RefCnt() {}int getRef_s() { return mCnt_s; }void addRef_s() { ++mCnt_s; }int delRef_s() { return --mCnt_s; }int getRef_w() { return mCnt_w; }void addRef_w() { ++mCnt_w; }int delRef_w() { return --mCnt_w; }};template <class T> class weak_ptr;//删除器template <class T>struct MyDeletor{public:void operator()(T* ptr) const{delete ptr;}};//Deletor默认为MyDeletor<T>template<class T, typename Deletor = MyDeletor<T> >class shared_ptr{private:T* mPtr;RefCnt<T>* mpRefCnt;Deletor myDeletor;public:shared_ptr(T* ptr = nullptr) :mPtr(ptr), mpRefCnt(nullptr){if (mPtr != nullptr){mpRefCnt = new RefCnt<T>(mPtr);}}~shared_ptr(){if (mPtr != nullptr && 0 == mpRefCnt->delRef_s() ){myDeletor(mPtr);if (mpRefCnt->getRef_w() == 0){delete mpRefCnt; //}}mPtr = nullptr;mpRefCnt = nullptr;}T& operator*()const { return *mPtr; }T* operator->() { return mPtr; }//拷贝构造,本身还没有被构建对象shared_ptr(const shared_ptr<T>& src) :mPtr(src.mPtr), mpRefCnt(src.mpRefCnt){//传入的智能指针的自愿如果是空的话,不会new RefCnt对象if (mPtr != nullptr){mpRefCnt->addRef_s();}}shared_ptr& operator=(const shared_ptr<T>& src){if (&src == this) return *this;if (mPtr != nullptr && 0 == mpRefCnt->delRef_s()){myDeletor(mPtr);mPtr = nullptr;myDeletor(mPtr);if (mpRefCnt->getRef_w() == 0) //这个要进行判断,不然使用weak_ptr的expired函数会出现问题{delete mpRefCnt; //}mpRefCnt = nullptr;}mPtr = src.mPtr;mpRefCnt = src.mpRefCnt;//必须要判断,因为传入的智能指针的自愿如果是空的话,不会new RefCnt对象if (mPtr != nullptr){mpRefCnt->addRef_s();}return *this;}shared_ptr(shared_ptr<T>&& src){mPtr = src.mPtr;mpRefCnt = src.mpRefCnt;src.mPtr = nullptr;src.mpRefCnt = nullptr;}shared_ptr<T>& operator=(shared_ptr<T>&& src){if (mPtr != nullptr && 0 == mpRefCnt->delRef_s()){myDeletor(mPtr);mPtr = nullptr;if (mpRefCnt->getRef_w() == 0){delete mpRefCnt; //}mpRefCnt = nullptr;}mPtr = src.mPtr;mpRefCnt = src.mpRefCnt;}// int use_count() { return mpRefCnt->getRef_s(); } errorint use_count(){if (mPtr != nullptr){return mpRefCnt->getRef_s();}return 0;}operator bool() const{return (mPtr != nullptr);}void reset(T* p = nullptr){if (mPtr != nullptr && 0 == mpRefCnt->delRef_s()){myDeletor(mPtr);mPtr = nullptr;if (0 == mpRefCnt->getRef_w()){delete mpRefCnt;}mpRefCnt = nullptr;}mPtr = p;mpRefCnt = nullptr; //一定要写if (mPtr != nullptr){mpRefCnt = new RefCnt(mPtr);}}void swap(shared_ptr<T>& src){std::swap(mPtr, src.mPtr);std::swap(mpRefCnt, src.mpRefCnt);}shared_ptr(const weak_ptr<T>& _w){mPtr = _w.mPtr;mpRefCnt = _w.mpRefCnt;mpRefCnt->addRef_s();}friend class weak_ptr<T>;};template <class T>class weak_ptr{private:T* mPtr;RefCnt<T>* mpRefCnt;void release(){if (mpRefCnt != nullptr){mpRefCnt->delRef_w();if (mpRefCnt->getRef_s() == 0 && 0 == mpRefCnt->getRef_w()){delete mpRefCnt;mpRefCnt = nullptr;}}mPtr = nullptr;mpRefCnt = nullptr;}friend class shared_ptr<T>;public:weak_ptr() :mPtr(nullptr), mpRefCnt(nullptr) {}weak_ptr(shared_ptr<T>& src) :mPtr(src.mPtr), mpRefCnt(src.mpRefCnt){if (mPtr != nullptr){mpRefCnt->addRef_w();}}weak_ptr(weak_ptr<T>& w) :mPtr(w.mPtr), mpRefCnt(w.mpRefCnt){if (mpRefCnt != nullptr){mpRefCnt->addRef_w();}}~weak_ptr(){release();}weak_ptr<T>& operator=(const shared_ptr<T>& s){release();mPtr = s.mPtr;mpRefCnt = s.mpRefCnt;mpRefCnt->addRef_w();}shared_ptr<T> lock() const{return shared_ptr<T>(*this);}int use_count() const{if (mPtr != nullptr){return mpRefCnt->getRef_s();}return 0;}bool expired() const //判断资源是否过期{return this->use_count() == 0;}};
}
2. 使用
(1)shared_ptr测试
class Object
{private:int value;
public:Object(int val = 0) :value(val) { cout << "construct Object" << endl; }~Object() { cout << "destruct Object" << endl; }int Value() { return value; }const int Value() const { return value; }
};
int main()
{jqw::shared_ptr<Object> obj(new Object(10)); //构造函数cout << obj->Value() << (*obj).Value() << endl; //* -> 10 10jqw::shared_ptr<Object> obj2(new Object(20)); cout << obj2->Value() << endl; //20obj2 = obj; //operator =cout << obj2->Value() << endl; //10jqw::shared_ptr<Object> obj3(obj2); //拷贝构造cout << obj3->Value() << endl; //10cout << obj.use_count() << endl; //use_count函数 3obj3.reset(new Object(30)); cout << obj3->Value() << " obj3.use_count():" << obj3.use_count() << " obj.use_count:" << obj.use_count() << endl;// 30 1 2obj3.swap(obj2);cout << obj2->Value() << " obj2.use_count():" << obj2.use_count() << " obj3.use_count:" << obj3.use_count() << endl;// 30 1 2return 0;
}
结果
:可以看到和文章开头系统提供的智能指针运行结果一致。
(2)weak_ptr测试
int main()
{jqw::shared_ptr<Object> objs1(new Object(10));jqw::shared_ptr<Object> objs2(new Object(20));jqw::weak_ptr<Object> objw(objs1);cout << objw.use_count() << endl; //1jqw::shared_ptr<Object> objs3 = objw.lock(); //lock()cout << objs1.use_count() << endl; //2objs1.reset();if (objw.expired())cout << "1过期" << endl;objs3.reset();if (objw.expired())cout << "2过期" << endl;return 0;
}
结果:和前面的一致。
总结
shared_ptr中delete mpRefCnt的时候要判断,否则weak_ptr的expired就会出错。
C++ shared_ptrweak_ptr的简单介绍和仿写相关推荐
- 瑞芯微RK3399K简单介绍及烧写镜像
瑞芯微RK3399K简单介绍及烧写镜像 文章目录 瑞芯微RK3399K简单介绍及烧写镜像 1. 前言 2. 官网及相关资料 3. 烧写固件所需软件 4. 直接烧写单一固件步骤 5. 固件文件 6. W ...
- 代理IP的背后原理简单介绍与python写一个获取代理IP的爬虫
title: 代理IP的那些事 copyright: true top: 0 date: 2019-11-13 14:20:39 tags: 代理IP categories: 爬虫笔记 permali ...
- 智能仿写软件-智能伪原创改写软件
智能仿写工具:营销创意的必备利器 在当今快节奏和不断发展的商业环境中,企业营销人员需要在短时间内产生大量有创意和高质量的内容.因此,智能仿写工具作为营销策略的一种创新方法而出现,可以帮助企业的写作团队 ...
- 基于X86的MikroTik ROS软路由的简单介绍和应用(一)
基于X86的MikroTik ROS软路由的简单介绍和应用 写在前面 其实基于X86开发的路由系统,很早就有一些企业开始做了,国内的比较出名的有海蜘蛛,维盟.爱快等等,但是今天介绍的呢,是国外的一个专 ...
- 计算机英语写一封邮件给汤姆作文,英语仿写一封信 介绍自己
敬爱的老师: 您好 您是我心目中最敬重的好老师,您虽然十分严厉,但是您把一颗心扑在我们身上. 您起早贪黑,琢磨着更好的教学方法. 您就像蜡烛一样,让我感到前途无限的光明,却燃烧了自己. 树木的成长,离 ...
- 自己动手写处理器之第一阶段(3)——MIPS32指令集架构简单介绍
将陆续上传本人写的新书<自己动手写处理器>(尚未出版).今天是第四篇.我尽量每周四篇 1.4 MIPS32指令集架构简单介绍 本书设计的处理器遵循MIPS32 Release 1架构,所以 ...
- 模拟jQuery,简单仿写API
jQuery是一个高效.精简并且功能丰富的 JavaScript 工具库.它提供的 API 易于使用且兼容众多浏览器,这让诸如 HTML 文档遍历和操作.事件处理.动画和 Ajax 操作更加简单.最近 ...
- 如何写好测试用例以及go单元测试工具testify简单介绍
背景 最近在工作和业余开源贡献中,和单元测试接触的比较频繁.但是在这两个场景之下写出来的单元测试貌似不太一样,即便是同一个代码场景,今天写出来的单元测试和昨天写的也不是很一样,我感受到了对于单元测 ...
- 关于在写代码时如何使用绝对路径与相对路径及其简单介绍
在写代码时,我们经常需要使用到其他的一些资源,但是对于一些像我这样经常用绝对路径不用相对路径的新手菜鸟来说,看网上的那些关于绝对路径相对路径的介绍资料也有点难理解,毕竟-没试过的话确实不大能理解. 所 ...
最新文章
- 正则表达式中的*,+,?以及\w和\W的区别等常见问题的总结
- 【DataBase】【SQL语言】【第三天】
- py+selenium 报错NameError: name 'NoSuchElementException' is not defined【已解决】
- 配置MGR启动第一个节点时start group_replication一直报ERROR 3092 (HY000):The server is not configured properly
- 投影元素直接隔离_Angular ngcontent 内容投影
- 中小企业利用VRRP实现链路负载均衡
- 编程中的一种特殊递归-尾递归
- Spring Security用户认证和权限控制(默认实现)
- C++编译时多态和运行时多态
- JavaScript数据结构和算法简述——数组
- 将流数据输出到Mysql中
- Python多线程好文
- 全局拉普拉斯平滑之(1)Strucutre extraction from texture via relative total variation及稀疏矩阵求解
- 智慧书-永恒的处世经典格言:121-160
- 在线画图工具ProcessOn
- 剧情插件Cutscene Creator uSequencer 1.3.7.1使用说明二
- 应用程序dll文件缺失,以及无法正常启动0xc000007b
- PM、GAN、InfoGAN、对抗自编码模型对比
- KingbaseES Clusterware 高可用案例之---构建iSCSI共享存储
- GMM-EM in Mnist