C++ allocator设计内存管理器

news2024/11/19 23:17:20

文章目录

  • allocator内存管理器
    • 基本属性
    • 类的设计
    • 关键功能的实现
    • 完整的内存管理器
  • 内存管理器的测试:设计自定义的String类。

前情回顾:
allocator内存管理类

allocator内存管理器

某些类需要在运行时分配可变大小的内存空间,一般来说我们使用容器如vector来管理我们的数据,但是对于某些类,在有些时候我们需要自己进行内存的分配。这些类必须定义自己的拷贝控制成员来管理所分配的内存。

我们将实现一个vector,这个vector保存的是sring类型的数据,而且使用我们自己创建的内存分配的方式。我们的这个类叫做 vecStr


基本属性

我们在自己创建的vecStr中需要有三个属性,分别是:

  • element: 指向分配的内存的起始位置。
  • first_free:指向已经构造完成的对象的下一个位置,即未构造的内存的起始位置。
  • cap:总的内存空间的尾后位置。

如图所示:
在这里插入图片描述

类的设计


基本构造函数:

strVec();
strVec(std::initializer_list<std::string> initList);
~strVec();
strVec(const strVec& other);			//拷贝构造函数
strVec& operator=(const strVec& other);	//拷贝赋值运算符

以下几个具有关键功能的函数:

  • alloc_n_copy:分配内存,并且拷贝给定范围的元素到一个新的内存中。
std::pair<std::string*, std::string*> alloc_n_copy(const std::string* beg, const std::string* end);
  • reallocate: 当内存不够时,重新分配一块新的内存,并且拷贝原始内容,释放旧的内存
void reallocate();
  • free:释放内存空间
//释放内存
void free();
  • check_n_alloc:检查当前内存空间是否足够,不够的话调用reallocate重新分配一块内存。
//检查内存是否足够,不够的话就重新分配
void check_n_alloc();

其他功能性函数:

//获取总容量
size_t capacity()const { return cap - element; }
//获取已分配的容量大小
size_t size()const { return first_free - element; }
void push_back(const std::string& str);

关键功能的实现


  • alloc_n_copy:接受一个开始位置和结束的位置的指针,开辟一块新的内存空间,并且把开始位置到结束位置中的内容拷贝到这块新的内存空间,并且返回这块内存空间的初始位置和尾后位置。

我们使用pair来保存这两个位置。

std::pair<std::string*, std::string*> strVec::alloc_n_copy(const std::string* beg, const std::string* end)
{
	auto p = allocStr.allocate(end - beg);		//分配end - beg个大小的内存空间,返回未构造的初始的位置
	//拷贝内存到新的内存空间,uninitialized_copy返回拷贝结束后的位置,这个位置就是first_free的位置,p就是element的位置
	return { p,std::uninitialized_copy(beg, end, p) };
}

使用uninitialized_copy来拷贝beg到end的内存空间的内容到新的内存空间p中。


  • reallocate:旧内存空间不够时,我们重新开辟一块内存,并且把原始内容拷贝到新内存中,注意我们的新内存一般是原始内存的两倍大小。
  1. 使用move的进行移动构造,从而避免拷贝构造(拷贝后销毁)的繁琐操作,直接进行指针所有权的转移即可,这就是移动构造函数。
  2. 使用construct构造对象。
  3. 注意属性的更新,element first_free cap此时都指向了新的内存空间的对应位置。
void strVec::reallocate()
{
	/*
	在重新分配空间的时候,移动而不是拷贝构造
	*/
	//申请两倍的空间
	auto NewSpace = (size() == 0) ? 1 : 2 * size();
	//分配新内存
	auto pNew = allocStr.allocate(NewSpace);
	auto dest = pNew;
	auto old = element;
	for (size_t i = 0; i != size(); i++)
	{
		//移动构造旧的内存里的数据,移动到新的内存空间里
		allocStr.construct(dest++, std::move(*old++));
	}
	free();	//释放旧内存

	//更新数据
	element = pNew;
	first_free = dest;
	cap = element + NewSpace;
}

  • free:释放旧的内存空间,我们使用三种方法来释放旧的内存空间,for_each函数式,正序销毁和逆序销毁,注意,销毁只是调用了他们的析构函数,我们一定最后使用deallocate来彻底释放这块内存。
void strVec::free()
{
	if (element)
	{
#if 0
		//for_each销毁
		std::for_each(begin(), end(), [&](std::string& str)
			{
				allocStr.destroy(&str);
			});
#elif 0
		//逆序销毁旧元素
		/*for (auto eleBeg = first_free; eleBeg != element;)
		{
			allocStr.destroy(--eleBeg);
		}*/
#else
		//正序销毁旧元素
		for (auto elebeg = element; elebeg != first_free;)
		{
			allocStr.destroy(elebeg++);
		}
#endif
		allocStr.deallocate(element, cap - element);
	}
}

完整的内存管理器

#pragma once
#include <iostream>
#include <vector>
#include <memory>
#include <vld.h>
#include <algorithm>

/*
内存管理器
*/
class strVec
{
public:
	strVec();
	strVec(std::initializer_list<std::string> initList);
	~strVec();
	strVec(const strVec& other);			//拷贝构造函数
	strVec& operator=(const strVec& other);	//拷贝赋值运算符
	//总容量
	size_t capacity()const { return cap - element; }
	//已分配的容量
	size_t size()const { return first_free - element; }
	void push_back(const std::string& str);
	//分配至少能容纳n个元素的内存空间
	void reserve(const int& n);
	//重新调整大小
	void resize(const int& n, const std::string& str = "None");
public:
	std::string* begin()const { return element; }
	std::string* end()const { return first_free; }
private:
	//分配内存,并且拷贝元素到这个范围里
	std::pair<std::string*, std::string*> alloc_n_copy(const std::string* beg, const std::string* end);
	//释放内存
	void free();
	//检查内存是否足够,不够的话就重新分配
	void check_n_alloc();
	//内存不够,分配新的内存空间:需要拷贝原来的元素并且释放原来的内存
	void reallocate();
	void reallocate(int n);
private:
	std::allocator<std::string> allocStr;	//内存分配器
	std::string* element;		//内存空间的起始元素
	std::string* first_free;	//已分配的实际元素之后的位置
	std::string* cap;			//总的分配空间之后的位置
};



strVec::strVec()
	:element(nullptr), first_free(nullptr), cap(nullptr)
{
}

strVec::strVec(std::initializer_list<std::string> initList)
{
	//分配合适的内存大小
	int n = initList.size();
	auto p = allocStr.allocate(n);
	element = first_free = p;
	cap = p + n;
	for (auto& xStr : initList)
	{
		allocStr.construct(first_free++, xStr);
	}
}

strVec::~strVec()
{
	free();
}

strVec::strVec(const strVec& other)
{
	//拷贝构造,other的内存拷贝到新的对象中
	auto pPair = alloc_n_copy(other.element, other.first_free);
	element = pPair.first;
	first_free = pPair.second;
	cap = pPair.second;			//alloc_n_copy分配的空间恰好容纳给定的元素
}

strVec& strVec::operator=(const strVec& other)
{
	//赋值运算符,直接对自身操作,返回自身,记得销毁原来的内存
	auto pPair = alloc_n_copy(other.element, other.first_free);
	free();
	element = pPair.first;
	first_free = cap = pPair.second;
	// TODO: 在此处插入 return 语句
	return *this;
}

void strVec::push_back(const std::string& str)
{
	//插入元素
	check_n_alloc();	//检查空间大小
	//构造对象
	allocStr.construct(first_free++, str);
}

void strVec::reserve(const int& n)
{
	//如果n小于等于当前容量,则什么也不做
	if (n > capacity())
	{
		//重新分配
		reallocate(n);
	}
}

void strVec::resize(const int& n, const std::string& str)
{
	if (n < capacity())
	{
		//如果说缩小容量,则删除后面的元素
		int m = capacity() - n;		//容量差值 15-10=5 则删除后五个元素
		for (int i = 0; i < m; i++)
		{
			allocStr.destroy(--first_free);
		}
		cap = first_free;
	}
	else
	{
		//否则增大容量,末尾填充str
		reallocate(n);
		while (first_free != cap)
		{
			allocStr.construct(first_free++, str);
		}
	}
}

std::pair<std::string*, std::string*> strVec::alloc_n_copy(const std::string* beg, const std::string* end)
{
	auto p = allocStr.allocate(end - beg);		//分配end - beg个大小的内存空间,返回未构造的初始的位置
	//拷贝内存到新的内存空间,uninitialized_copy返回拷贝结束后的位置,这个位置就是first_free的位置,p就是element的位置
	return { p,std::uninitialized_copy(beg, end, p) };
}

void strVec::free()
{
	if (element)
	{
#if 0
		//for_each销毁
		std::for_each(begin(), end(), [&](std::string& str)
			{
				allocStr.destroy(&str);
			});
#elif 0
		//逆序销毁旧元素
		/*for (auto eleBeg = first_free; eleBeg != element;)
		{
			allocStr.destroy(--eleBeg);
		}*/
#else
		//正序销毁旧元素
		for (auto elebeg = element; elebeg != first_free;)
		{
			allocStr.destroy(elebeg++);
		}
#endif
		allocStr.deallocate(element, cap - element);
	}
}

void strVec::check_n_alloc()
{
	//内存不够
	if (size() == capacity())
	{
		reallocate();
	}
}

void strVec::reallocate()
{
	/*
	在重新分配空间的时候,移动而不是拷贝构造
	*/
	//申请两倍的空间
	auto NewSpace = (size() == 0) ? 1 : 2 * size();
	//分配新内存
	auto pNew = allocStr.allocate(NewSpace);
	auto dest = pNew;
	auto old = element;
	for (size_t i = 0; i != size(); i++)
	{
		//移动构造旧的内存里的数据,移动到新的内存空间里
		allocStr.construct(dest++, std::move(*old++));
	}
	free();	//释放旧内存

	//更新数据
	element = pNew;
	first_free = dest;
	cap = element + NewSpace;
}

void strVec::reallocate(int n)
{
	auto NewSpace = n;
	//分配新内存
	auto pNew = allocStr.allocate(NewSpace);
	auto dest = pNew;
	auto old = element;
	for (size_t i = 0; i != size(); i++)
	{
		//移动构造旧的内存里的数据,移动到新的内存空间里
		allocStr.construct(dest++, std::move(*old++));
	}
	free();	//释放旧内存

	//更新数据
	element = pNew;
	first_free = dest;
	cap = element + NewSpace;
}

内存管理器的测试:设计自定义的String类。

/*
char* 类型的内存分配器
*/

class String
{
public:
	String();
	String(const char* str);
	String(const String& other);
	String& operator=(const String& other);
	~String();
	size_t size() const { return end - element; }
	//重新分配内存
public:

private:
	void free();
	std::pair<char*, char*> alloc_n_copy(const char* beg,const char* end);
	//void reallocapacity();
	char* element;
	char* end;
	std::allocator<char> alloCh;	//内存分配器
};

int main()
{
	String s{"woaini"};
	String s1{ s };
	String s2;
	s2 = s1;
	return 0;
}

String::String()
	:element(nullptr),end(nullptr)
{
}

String::String(const char* str)
{
	//分配内存
	int len = strlen(str);
	auto pStr =  alloc_n_copy(str, str + len);
	element = pStr.first;
	end = pStr.second;
}

String::String(const String& other)
{
	auto pNew = alloc_n_copy(other.element, other.end);
	element = pNew.first;
	end = pNew.second;
}

String& String::operator=(const String& other)
{
	auto pNew = alloc_n_copy(other.element, other.end);
	free();
	element = pNew.first;
	end = pNew.second;
	return *this;
}

String::~String()
{
	free();
}

void String::free()
{
	if (element)
	{
		std::for_each(element, end, [&](char& str)
			{
				alloCh.destroy(&str);
			});
		alloCh.deallocate(element, end - element);
	}
}

std::pair<char*, char*> String::alloc_n_copy(const char* beg, const char* end)
{
	auto p =  alloCh.allocate(end - beg);
	return { p,std::uninitialized_copy(beg,end,p) };
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/106646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零搭建完整python自动化测试框架(UI自动化和接口自动化)

从零搭建完整python自动化测试框架&#xff08;UI自动化和接口自动化&#xff09; 文章目录 总体框架 PO模式、DDT数据驱动、关键字驱动 框架技术选择 框架运行结果 各用例对应的定义方式&#xff08;PO/DDT&#xff09; 测试执行结果 从零开始搭建项目 一、开发环境搭…

泪目,终于有P8大佬把困扰我多年的《计算机网络原理》全部讲明白了

前言 为什么网络协议这么重要呢&#xff1f;集群规模一大&#xff0c;我们首先想到的就是网络互通的问题&#xff1b;应用吞吐量压不上去&#xff0c;我们首先想到的也是网络互通的问题。所以&#xff0c;要成为技术牛人&#xff0c;搞定大系统&#xff0c;一定要过网络这一关&…

Mac怎么清理缓存?这两种方法都非常好用哦

与电脑系统或应用程序非常相似&#xff0c;您的Mac也有自己的系统缓存&#xff0c;它可以在后台临时存储数据&#xff0c;以加快软件安装速度并减少互联网数据使用量&#xff08;通过Apple&#xff09;。与电脑系统或应用程序类似&#xff0c;缓存数据可能会开始堆积——占用存…

unordered系列关联式容器以及哈希表原理实现

Ⅰ. unordered 系列关联式容器 在C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可达到 log2nlog_2 nlog2​n&#xff0c;即最差情况下需要比较红黑树的高度次&#xff0c;当树中的节点非常多时&#xff0c;查询效率也不理想。最好的…

Android Studio Profiler 检查内存

Android Studio Profiler 检查内存简单介绍 如何使用&#xff1f; 第一步&#xff1a;点击Profiler按钮 第二步&#xff1a;选择 第三步&#xff1a;选择Capture heap dump 并点击Record 解释相关按钮的功能 垃圾桶按钮&#xff1a;用于强制执行垃圾回收事件的按钮&#xff…

LinkedList(JDK1.8)源码+底层数据结构分析

文章目录前言一、双向链表1.1 双向链表示意图1.2 LinkedList 属性1.3 Node 节点对象二、双向链表的操作2.1 添加元素-add2.2 删除元素-remove2.3 修改元素-set2.4 查询元素-get前言 双向链表是一种数据结构&#xff0c;由若干个节点构成&#xff0c;其中每个节点均由三部分构成…

疯狂游戏笔试题-2022秋招

编程题 1.假设数组第一个元素是k, 如果k在数组内, 则k*21 和 k*31也在数组内. 在已知k的情况下, 需算出另一个数是否也在数组内? 例子: 输入1,2 输出False 输入1,4 输出True 解题思路&#xff1a;暴力&#xff08;doge&#xff09;,实在想不到其它好方法&#xff0c;有…

生成模型详解

一、生成模型的定义 给定的训练集X{x1,x2,...,xn}X \{x^1,x^2,...,x^n\}X{x1,x2,...,xn}隐变量zzz满足p(z)N(0,I)p(z) \mathcal{N} (0,I)p(z)N(0,I)定义一个条件分布pθ(x∣z)p_{\theta}(x|z)pθ​(x∣z)&#xff0c;θ\thetaθ可以理解为生成模型的参数训练好模型后&#xff…

java高校宿舍费缴纳报修管理系统ssm1561

系统选用B/S模式&#xff0c;应用jsp技术&#xff0c; MySQL为后台数据库。系统主要包括个人中心、学生管理、宿管管理、宿舍信息管理、宿舍预订管理&#xff0c;在线报修管理、费用缴纳管理、投诉建议管理、论坛交流、系统管理等功能模块。 本系统采用从上往下的步骤开发&…

爬虫学习-数据解析三种方式:正则、bs4、xpath,以及一些实例操作

若出现乱码page_text page_text.encode(iso-8859-1).decode(gbk)或者查看源码head里面的说明&#xff0c;设置成相同的即可 数据解析原理概述 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行储存数据解析就是 1、进行指定标签的定位2、标签或者标签对应的属性中…

设计模式之美总结(结构型篇)

title: 设计模式之美总结&#xff08;结构型篇&#xff09; date: 2022-12-21 09:59:11 tags: 设计模式 categories:设计模式 cover: https://cover.png feature: false 文章目录1. 代理模式&#xff08;Proxy Design Pattern&#xff09;1.1 原理解析1.2 动态代理1.3 应用场景…

排查Java服务CPU使用率高达100%的原因

排查Java服务CPU使用率高达100%的原因 Java服务在服务器运行一段时间&#xff0c;有一天CPU使用率突然高达100%&#xff0c;通过jstack工具分别在CPU使用率为100%时执行了一次堆线程dump和cpu使用率降下来后执行了一次堆线程dump 目录排查Java服务CPU使用率高达100%的原因一、环…

【SQL】一文详解嵌入式SQL(建议收藏)

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

SQLMap 扫描利用SQL注入

一、SQLMap介绍 SQLMap 是一个自动化的SQL注入工具&#xff0c;其主要功能是扫描、发现并利用给定URL的SQL注入漏洞&#xff0c;内置了很多绕过插件&#xff0c;支持的数据库是MySQL 、Oracle 、PostgreSQL 、Microsoft SQL Server、Microsoft Access 、IBM DB2, SQ Lite 、Fir…

光伏行业管理亟待变革,数商云供应链系统订单流程自动化流转助力企业降本增效

作为实现“3060”双碳目标的主力军&#xff0c;光伏产业正迎来空前的政策、市场、资本三重加持的红利期。有业内人士预测&#xff0c;到2025年全球新增光伏装机量将达到270-330GW&#xff0c;国内新增光伏装机量将达到90-110GW&#xff0c;十四五期间年均新增光伏装机量将达到7…

用React做一个音乐播放器

介绍 任何正在学习 React 并想使用 React 构建项目的人。有各种博客和文章可以为开发人员指导此类项目。我确实浏览过这些文章&#xff0c;但其中总是缺少一种项目。缺少的项目是音乐播放器和视频播放器。这两个项目都会让您有机会处理音频和视频。您将学到很多东西&#xff0…

Linux学习-97-vmware网络桥接模式配置和vmware快照操作

19.3 vmware网络桥接模式配置 桥接&#xff1a;需要保证Linux虚拟机和本机处在同一个网段&#xff01; #win平台输入ipconfig查看主机的ip地址Linux也必须要配置到对应的网段 桥接模式&#xff1a;主机ip 和虚拟机ip映射到同一块物理网卡&#xff08;光纤&#xff0c;无线…

达梦数据库-centos7安装

参考官方文档 1.环境 操作系统CPU数据库CentOS7x86_64dm8_20221121_x86_rh6_64.iso 2.安装前准备 2.1 关闭防火墙 或 开放5236端口 # 关闭防火墙 systemctl stop firewalld systemctl disable firewalld # 开放5236端口&#xff08;推荐使用&#xff09; firewall-cmd --pe…

TYPE A USB2.0电路设计

TYPE A USB2.0连接器可以分为公座和母座&#xff0c;放在供电端PCB板上的一般是母座&#xff0c; 2.0的母座有四个信号引脚。今天就来和大家分享下TYPE A USB2.0的板级电路设计&#xff0c; 首先来看到 TYPE A USB2.0四个引脚的信号定义&#xff0c;1脚是VBUS&#xff0c;需要…

Kafka系列之入门(荣耀典藏版)

目录 一、为什么要用消息中间件&#xff1f; 1、异步处理 2、应用解耦 3、流量削峰 4、日志处理 二、为什么选择Kafka&#xff1f; 消息中间件的编年史 1、Kafka的外在表现和内在设计 2、市场主流消息中间件对比 三、Kafka中的基本概念 1、消息和批次 2、主题和分区…