动态内存管理和智能指针 2.0 -- shared_ptr
shared_ptr出现原因
通过第一章的学习,我们知道不管是auto_ptr合适scoped_ptr都是存在缺陷的,于是我们必须想出一个方法既能很好的管理我们的内存,而且在使用的时候,可以多个指针指向一个内存,这个时候就出现了shared_ptr。
shared_ptr的实现原理
shared_ptr使用的引用计数的浅拷贝的形式,这个时候是不需要使用引用计数的写时拷贝的,因为多个指针指向的是同一个动态的内存空间,当其中的一个内存空间改变的时候,其他的内容也是相应的改变的。
这个时候我们的shared_ptr这个类的成员变量中就要有一个指针,用于指向一个动态开辟的存储空间,还需要有一个用于计数的指针,这个指针指向一个动态开辟的内存空间,一般是整型的,这个整型 变量中存放的是我们指向同一个空间的个数,然后这个动态的整型空间只在构造函数的使用开辟出来,其他的拷贝构造函数还有赋值运算符的重载 的时候直接的使用了。
简单代码实现
template<class T>
class Shared_Ptr
{
public:Shared_Ptr(T* ptr):_ptr(ptr),_count(new int(1)){}Shared_Ptr(const Shared_Ptr& ptr):_ptr(ptr._ptr), _count(ptr._count){(*_count)++;}Shared_Ptr& operator=(const Shared_Ptr& ptr){if (this == &ptr){return *this;}else{if (!--(*_count)){delete _ptr;delete _count;cout << "delete older" << endl;}_ptr = ptr._ptr;_count = ptr._count;(*_count)++;}return *this;}~Shared_Ptr(){(*_count)--;if ((*_count) == 0){delete _ptr;delete _count;cout << "delete" << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _count;
};
代码分析
构造函数
当我们使用构造函数的时候,这个时候肯定是已经动态开辟了一个内存空间的,所以我们这个时候也给我们_count指针动态的开辟一个空间,并且这个空间的值是1,因为此时一定是有一个空间了。
拷贝构造函数
拷贝构造函数的时候,我们是让一个已经存在的对象去初始化另一个对象,所以这个时候我们只需要让当前对象的指针指向那个动态的空间,同时时当前对象的计数指针也指向那个对象的计数空间,并且使当前对象的计数值加1,因为这个时候已经有两个对象 指向了一个动态的空间了。
析构函数
析构函数的时候,我们需要把引用计数减一,这个时候再去判断我们的引用计数的值是否为0,如果是0,这个时候就需要释放我们的动态管理的空间,同时释放掉我们的引用计数的动态空间,同时需要把这两个指针置为NULL。
赋值运算符的重载
该检查的是,这个赋值是不是自赋值,如果是 自赋值,这个时候直接返回该对象 即可了,还需要注意的是,我们 的引用计数不需要加1.如果不是自赋值,此时需要把当前对象的引用计数减1,同时判断减1之后应用计数是不是为0,如果是0则需要释放掉,如果不是0,就不要管了,然后接下来把我们当前指针指向被赋值的那个空间即可。
循环引用和weak_ptr
当下面的代码的时候会出现一种情况就是 循环引用,首先看代码,下面的代码是我们定义的一个结构体
struct Str
{Shared_Ptr<Str> _prev;Shared_Ptr<Str> _next;
};
再来看我们的构造函数
Shared_Ptr(T* ptr):_ptr(ptr),_count(new int(1)){}
上面的构造函数中,我们的构造函数没有缺省值,会报错,因为我们在定义_prev和_next的时候,没有传入参数,所以我们需要把我们的构造函数改成下面的样子,就是把缺省值赋值为NULL。
这个时候我们写一个测试用例
Shared_Ptr<Str> a = new Str;
Shared_Ptr<Str> b = new Str;
这个时候会打印出六个delete,因为我们new出来的两个对象本身是有两个对象的,所以析构的时候,会析构这里面的两个一个是_next一个是_prev,然后我们的a和b本身也是指向对象的,所以一共析构了六次。
隐患问题 – 循环引用
如果我们是像下面的方式 使用它,就会出现循环 引用的问题,请看下面的代码
Shared_Ptr<Str> a = new Str;Shared_Ptr<Str> b = new Str;a->_next = b;b->_prev = a;
结合刚刚的分析,我们来分析上面的一段程序,首先是a和b分别指向了两个new出来的对象 ,然后这两个对象 里面的next和prev分别指向了两个对象,接着 执行a->_next = b;
b->_prev = a;的时候,会调用赋值运算符的重载,然后就是a里面的next由原来的内容指向了b,b里面 的prev由原来的内容指向了a;这个时候问题就来了,b这个指向指向的内存空间有两个指针指向着,一个是b自己,一个是a->_next,所以析构的时候不会释放内存空间,这不是我们想看到的结果。也可以这样子分析,就是我们的a和b析构的时候,只是 把引用计数减1,接下来析构a->_next和b->prev的时候,都是相互依赖彼此的,所以都释放不了,这就是循环引用。
weak_ptr
于是为了解决上面的循环引用的特殊场景,配合着shared_ptr设计出了一个weak_ptr,代码如下
#include<iostream>
using namespace std;template<class T>
class Weak_ptr;template<class T>
class Shared_Ptr
{public:friend class Weak_ptr<T>;Shared_Ptr(T* ptr = NULL):_ptr(ptr),_count(new int(1)){}Shared_Ptr(const Shared_Ptr& ptr):_ptr(ptr._ptr), _count(ptr._count){(*_count)++;}Shared_Ptr& operator=(Shared_Ptr& ptr){if (this == &ptr){return *this;}else{if (!--(*_count)){delete _ptr;delete _count;cout << "delete older" << endl;}_ptr = ptr._ptr;_count = ptr._count;(*_count)++;}return *this;}~Shared_Ptr(){(*_count)--;if ((*_count) == 0){delete _ptr;delete _count;_ptr = NULL;_count = NULL;cout << "delete" << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}protected:T* _ptr;int* _count;
};template<class T>
class Weak_ptr
{
public:Weak_ptr():_ptr(NULL){}Weak_ptr(Shared_Ptr<T> ptr){_ptr = ptr._ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};struct Str
{Weak_ptr<Str> _prev;Weak_ptr<Str> _next;int _a;
};void TestPtr()
{Shared_Ptr<Str> a = new Str;Shared_Ptr<Str> b = new Str;a->_next = b;b->_prev = a;a->_next->_a = 0;cout << b->_a;}
首先我们看Weak_ptr,它的成员变量是T* _ptr;此时我们的Str那个自定义的结构体中的指针就可以改成了Weak_ptr的形式,因为我们的Weak_ptr维护的是一个普通的指针,但是我们在使用的时候需要用到_next指向一个Shared_ptr的对象,所以这个时候,我们不要把Weak_ptr的拷贝构造函数的参数写成是Shared_ptr,然后赋值的时候,需要把Shared_ptr的_ptr赋值给Weak_ptr的_ptr,但是Shared_ptr中的_ptr是 私有的,所以这个时候我们在Shared_ptr里面把Weak_ptr声明为友元。但是这个时候又出现了一个 问题就是,我们的他声明为友元之后,编译器找不到我们的友元类,因为我们的Weak_ptr的定义部分是在Shared_ptr的后面,所以这个时候,需要在Shared_ptr的前面声明我们的Weak_ptr。
定制防函数
所谓的防函数就是让我们的类看起来像是函数一样
举一个简单的例子,看下面的代码
struct Compare
{bool operator()(int a,int b){return a > b;}
};void test()
{Compare com;cout << com(1,2);
}
类Compare是我们定制的一个防函数,下面的test就是我们把他 当成一个函数来使用它
为什么要引入防函数呢,因为我们在使用的时候上面的Shared_ptr的时候,我们管理的内存 空间可能是一个FILE*的一个指针,这个时候我们就不能只使用delete来释放我们的空间,这个时候我们就需要定制一个防函数,把它作为一个参数放在构造函数 中,同时我们的Shared_ptr的成员变量里面需要定义一个这样的变量。
请看下面的代码
#include<iostream>
using namespace std;
#include<assert.h>
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1struct DelFile
{void operator()(FILE* f){fclose(f);cout << "fclose" << endl;}
};struct DelDel
{void operator()(void* p){assert(p);delete p;cout << "delete" << endl;}
};template<class T,class Del>
class Weak_ptr;template<class T,class Del>
class Shared_Ptr
{public:friend class Weak_ptr<T,Del>;Shared_Ptr(T* ptr,Del d):_ptr(ptr),_count(new int(1)), _del(d){}Shared_Ptr(const Shared_Ptr& ptr):_ptr(ptr._ptr), _count(ptr._count){(*_count)++;}Shared_Ptr& operator=(Shared_Ptr& ptr){if (this == &ptr){return *this;}else{if (!--(*_count)){del(_ptr);delete _count;cout << "delete older" << endl;}_ptr = ptr._ptr;_count = ptr._count;(*_count)++;}return *this;}~Shared_Ptr(){(*_count)--;if ((*_count) == 0){_del(_ptr);delete _count;_ptr = NULL;_count = NULL;//cout << "delete" << endl;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}protected:T* _ptr;int* _count;Del _del;
};template<class T, class Del>
class Weak_ptr
{
public:Weak_ptr():_ptr(NULL){}Weak_ptr(Shared_Ptr<T,Del> ptr){_ptr = ptr._ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};struct Str
{Weak_ptr<Str,DelDel> _prev;Weak_ptr<Str, DelDel> _next;int _a;
};void TestFile()
{DelFile d;Shared_Ptr<FILE, DelFile> a(fopen("w.ss","w"),d); //注意这里传参的时候,首先要实例化一个对象//我一开始使用的是Shared_Ptr<FILE, DelFile> a(fopen("w.ss","w"),DelFile d)//这种方式显然是错误的,我不能在一个函数里面去实例化一个对象
}void TestPtr()
{DelDel d;Shared_Ptr<Str, DelDel> a(new Str, d);Shared_Ptr<Str, DelDel> b(new Str, d);a->_next = b;b->_prev = a;a->_next->_a = 0;cout << b->_a;}struct Compare
{bool operator()(int a,int b){return a > b;}
};void test()
{Compare com;cout << com(1,2);
}
相关文章:

汇总同一时间段的数据_数据集干货:一文读懂Mapsidejoin
我们知道数据分析的第一步是准备数据,所以在前面的课程里,我们介绍了元数据。今天这篇文章,主要介绍大数据量组合数据集在永洪中的应用实例:Mapsidejoin。什么是Mapsidejoin?按照字面意思,Mapsidejoin就是M…

【强烈推荐】国土档案管理信息系统产品使用说明书系列目录【附下载地址】...
<<国土档案管理信息系统>>产品使用说明书系列目录【附下载地址】——通过知识共享树立个人品牌。《国土档案管理信息系统》在线视频讲解一、记大型商业软件<<国土档案管理信息系统>>之系统简介记大型商业软件 > 之系统简介 ——通过知识共享树立个人…

zip函数的使用
s [[1, 10], [1.2, 11], [2, 5], [5, 15]] data zip(*s) x_list data[0] y_list data[1] x_min min(x_list) x_max max(x_list) y_min min(y_list) y_max max(y_list) box [x_min, x_max, y_min, y_max] print(box) # [1, 5, 5, 15] 转载于:https://www.cnblogs.com/…

计算机网络基础 1.0 -- 概述
概念理解 报文:在网络中发送的数据块成为报文在发送报文之前,通常会把数组分组,每个组都有个包头和数据组成,包头中包含了诸如目标地址和源地址等重要信息,这样才保证了数据能够有目的的在网络中的传输主机是用户用来…

字符串循环同构的最小表示法(转)
循环字符串的最小表示法的问题可以这样描述: 对于一个字符串S,求S的循环的同构字符串S’中字典序最小的一个。 由于语言能力有限,还是用实际例子来解释比较容易:设Sbcad,且S’是S的循环同构的串。S’可以是bcad或者cad…

周长相等的正方形面积一定相等_必考单元:三年级下册面积计算公式+知识点+测试卷(附答案),重点内容,收藏练习!...
《面积》公式 知识点面积和面积单位:1.常用的面积单位有:(平方厘米)、(平方分米)、(平方米)。2.理解面积的意义和面积单位的意义。面积:物体表面或封闭图形的大小,叫做它们的面积。1平方米:边长是1米的正方形ÿ…

sql server 2000 版本查询
确定已安装的 SQL Server 2000 Database Components 版本 使用 isql、osql 或查询分析器,对数据库引擎实例执行以下查询之一。 SELECT SERVERPROPERTY(ProductLevel) SELECT VERSION SELECT SERVERPROPERTY(Produc…

ubuntu16创建开机启动服务
1、cd /etc/init.d/ 2、sudo touch zookeeper(举例) 3、给服务赋权限:sudo chmod x zookeeper 4、执行sudo vim zookeeper 命令写入执行脚本(启动脚本中的启动命令对应服务的启动命令) #! /bin/sh### BEGIN INIT INFO …

Effective C++ 1.0 -- 概述
声明 对象声明,函数声明,类型声明,是告诉编译器某个东西的 名称和类型,但是略去了实现 细节,因为定义在其他的地方。 external int x; //对象(object)声明 std:size_t numDigits(int num…

寻找连通域算法_【车牌识别算法】
车牌识别技术要求能够将运动中的汽车牌照从复杂背景中提取并识别出来,通过车牌提取、图像预处理、特征提取、车牌字符识别等技术,识别车辆牌号、颜色等信息。目前车牌识别技术主要分为端到端识别与车牌分割识别两种识别算法。端对端识别技术端到端车牌识…

SQL 2005 删除带有默认值约束的列
在sqlserver 2005中要删除某一列: alter table [表名] drop column [列名] 但是如果该列被创建了默认值约束,我们就只能先删除默认值约束,然后才能删除该列。 第一种方法: 1、删除默认值约束: DECLARE name varchar(100) SELECT nameb.name F…

apple hosts
2019独角兽企业重金招聘Python工程师标准>>> #Apple #125.56.202.7 swcnd.apple.com 203.69.138.34 a1.phobos.apple.com 203.69.138.34 a2.phobos.apple.com 203.69.138.34 a3.phobos.apple.com 203.69.138.34 a4.phobos.apple.com 203.69.138.34 a5.phobos.apple.…

thymeleaf 的常见属性
转载于:https://www.cnblogs.com/hwgok/p/9637723.html

继承和多态 1.0 -- 继承概念(is-a、has-a,赋值兼容规则,隐藏重定义)
普通继承和访问权限 当一个继承没有虚拟继承或者是多重继承时,就是一个简单的继承的时候,这个时候就是一个普通的继承。 普通继承的内存空间是:子类的对象中,包含了父类的成员变量,同时也可以调用父类的成员函数&…

c++标准库 及 命名空间std
1、命名空间std C标准中引入命名空间的概念,是为了解决不同模块或者函数库中相同标识符冲突的问题。有了命名空间的概念,标识符就被限制在特定的范围(函数)内,不会引起命名冲突。最典型的例子就是std命名空间,C标准库中所有标识…

aop point 只能获取到map嘛_面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?...
这篇文章会从下面从以下几个问题展开对 IoC & AOP 的解释什么是 IoC?IoC 解决了什么问题?IoC 和 DI 的区别?什么是 AOP?AOP 解决了什么问题?AOP 为什么叫做切面编程?首先声明:IoC & AOP…

HTML学习笔记_002_如何学习HTML
.htm 还是 .html 文件后缀? 当您保存 HTML 文件时,既可以使用 .htm 也可以使用 .html 文件后缀。我们在实例中使用 .htm。这只是长久以来形成的习惯而已,因为过去的很多软件只允许三个字母的文件后缀。 不过对于新的软件,使用 .ht…

设计1.0 -- iterator 和const_iterator底层的模拟实现
本文概要: 本文主要是模拟实现STL中迭代器和const迭代器的,主要阐述的一个问题就是,为什么我们在设计迭代器的时候需要使用三个模板参数呢 在设计迭代器的时候,我们有下面的代码 #include<iostream> using namespace std…

关于python缩进的描述中_关于Python程序中与“缩进”有关的说法中,以下选项中正确的是()...
关于Python程序中与“缩进”有关的说法中,以下选项中正确的是()答:缩进在程序中长度统一且强制使用同文学或同音乐主题的民歌,《_______》是其中之一。此曲经民间艺人丁喜才改编后,由上海音乐学院鞠秀芳于50年代在国际上唱红答:五…

使用Newtonsoft.Json
首先是前一段某博友对于循环引用的问题的解决:http://www.cnblogs.com/Gryzor/archive/2013/05/06/3062373.html 其次是时间到底是UTC还是Local的问题:http://www.cnblogs.com/leonwang/archive/2013/05/29/javascript-datetime.html 对于第二条…

回击质疑 HP StoreOnce用高性能说话
四五年以前,记者曾经两次参观过惠普位于英国西南部港口城市布里斯托尔的存储研究中心,那里是惠普数据备份和保护产品的大本营,同时也是惠普磁带产品与HP StoreOnce产品家族的诞生地。就在惠普刚刚发布升级版的HP StoreOnce B6200以及Catalyst…

windows10配置jenkins
1、下载pscp.exe并拷贝到c:\window\system32,此工具用于上传文件至linux服务器 下载地址:https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html 2、下载jenkins.war 下载地下:https://jenkins.io/download/ 3、设置环境变量JENKI…

STL模拟实现1.0 -- list和iterator模拟实现和简单分析
引言 C 标准模本库《STL》中有很多优秀的代码实现,不然怎么能叫做C标准模板库呢,其中一个实现就是有一个容器,叫做list。所谓容器其实就是存储相同类型数据的一个存储集合,list的底层实现其实就是一个链表。 我们的普通数组在使…

hdc mfc 画扇形图_使用echarts绘制条形图和扇形图
使用echarts绘制条形图和扇形图简单举例说明下echarts如何绘制条形图和扇形图代码示例echarts绘制条形图和扇形图var mychart1echarts.init(document.getElementById(chart1),light);// 指定图表的配置项和数据var option {title: {text: ECharts 入门示例},tooltip: {},legen…
Java学习笔记45:Java 线程与主线程之间的变量关系
运行下面的代码: package com.test.www;public class Test {public static int count 0;public static void inc() {//这里延迟1毫秒,使得结果明显try {Thread.sleep(1);} catch (InterruptedException e) {}count;}public static void main(String[] a…

会计的思考(36):会计--企业运营的数码相机
新浪网上看到一张图片,拍的是2009年12月3日的武汉市,整个城市在笼罩在一片污浊空气当中,让人震惊。虽然我在深圳,但也清楚好不到哪去。 震惊之余,感谢新浪,更感谢某位摄影师,让我们了解到生活的…

spring cloud连载第二篇之Spring Cloud Config
Spring Cloud Config Spring Cloud Config为分布式服务提供了服务侧和客户侧的外部配置支持。通过Spring Cloud Config你可以有一个统一的地方来管理所有应用的外部配置。 默认服务端存储实现用的是git,因此,它很容易支持配置环境的标签版本,…

CodeOnly
关于设置[Key]标志 要先添加 程序集的引用 在添加 using System.ComponentModel.DataAnnotations; 命名控件转载于:https://www.cnblogs.com/since87/archive/2013/06/09/3129399.html

继承和多态 2.0 -- 继承的六个默认成员函数
本文重要介绍普通继承中如何写派生类的六个默认成员函数,主要是针对在派生类中,如何调用基类的六个默认成员函数 需要说明的一点就是,如果子类中没有调用父类的函数时,系统会自动生成一个。 构造函数 子类中有父类的成员&#…

js 让鼠标右下角有一排小字_js布局中一排大字下面接着一排小字怎么打出来?...
展开全部可以使用 TypewriterJS 实现效果是这样 ,百度图片少了一部分,可以通过链接32313133353236313431303231363533e78988e69d8331333433626438看效果链接: 网页链接示例在这里: 网页链接文档地址: 网页链接使用步骤引用 TypewriterJShtmlcss.article__title {fon…