.在上一节我们实现的 MyVector存在哪些问题?

问题1

现在有Student类
class Student{
public:
Student(){cout<<"构造Student对象"<<endl;}
~Student(){cout<<"析构Student对象"<<endl;}
private:
int age
char NAME[20;]
char *pAddress;
} MyVector<Student> v1[1000];

我只是希望创建一个能放1000个Student 的Vector,但是开始并不放任何内容,但是发现编译器除分配了1000个student对象的空间,还创建了1000个对象,

在main函数结束后再析构这1000个对象,这就是问题1,这1000个对象并不是我需要的,

原因如下,在MyVector的构造函数中 T * _tep = new T[size](); 这个new除了分配空间,还会调用对象的构造函数完成对象的初始化

换句话说就是 空间的分配和对象的创建联系在了一起,这个非常不合理,我们需要把他分开,我希望是需要帮我开辟空间即可,不希望帮我创建1000个对象

问题2关于析构函数

//析构函数
~MyVector<T>() {
if (Empty() == false) {
delete[]first;
first = nullptr;
last = nullptr;
end = nullptr;
cout << "析构Vector,堆地址"<<first << endl;
} }

实际情况时,我的vector是可以放1000个student对象的容器,但是实际里面可能暂时只放了100个对象,

而delete[] first,是把整个vector中的1000个student对象都调用析构函数,这也不合理,我们只需要析构有效

数量的对象然后再释放first指针指向的堆内存

问题3 关于添加元素和删除元素

student s1;
student s2;
student s3; MyVector<student> v1(1000);// 这一句编译器会先分配1000个student对象空间,然后再调用1000次构造函数创建1000个student对象 v1.pushback(s1);
//上面这句话会调用赋值函数,会将s1的内容赋值给已经在堆上的student对象,但实际上我想要的是,只需要将分配好的空间
//给我,我使用拷贝构造,将s1拷贝构造出来一个对象放在你给的空间上,这是个问题 v1.popBack(){
this->last--;
}
//上面的pop最后一个studnet对象,只是将最后一个元素的指针前移动一个student空间大小,并没有去析构这个对象,
//而且这个弹出的student对象里面有一个char *pAddress 指向了外部堆内存资源,这个内存自己没有去释放的话,
//造成了内存泄漏了. 这个也是个问题,所以说我们要去析构这个弹出的student对象但是不用使用 delete 方式 delete 这个对象,为什么呢?
//应为delete 处理调用析构函数完,他还要释放内存空间,但是这个空间属于Vector的,释放掉了这一小块空间,我们后面就无法在使用了,
//**所以一句话就是我们从vector中删除一个对象时,只做析构这个对象而不释放他的内存空间,把对象的析构和内存的释放分开**

以上三个问题使得我们不能采用上一篇文字中的方式写Vector,这就是引入了容器的空间配置器的原因

容器的空间配置器做了4件事

内存开辟/内存释放 对象创建/对象析构

在上一遍的基础上加入空间配置器,代码如下

#include <iostream>
using namespace std; class student { public: student(int _age, char * _pname):
age(_age),
pName(_pname){
} student(const student & _rvalue) {
this->age = _rvalue.age;
this->pName = new char[20];
strcpy(this->pName, _rvalue.pName);
} student & operator =(const student & _rvalue) { if (this == &_rvalue) { return *this; } delete[]this->pName;
this->pName = nullptr; this->age = _rvalue.age;
this->pName = new char[20];
strcpy(this->pName, _rvalue.pName);
}
~student() {
delete[] this->pName;
this->pName == nullptr;
cout << "student 析构函数被执行,当前对象地址是" << this << endl;
} private:
int age;
char *pName;
}; template<typename T>
class MyAllocate { public: T * allocate(int size) {
return (T *)malloc(sizeof(T)*size);
} //根据指定的地址,释放内存空间
void delAllocate(T *p) {
free(p);
} //在p指针指定的位置,根据指定的对象创建新对象
void construct(T * p, const T & _rValue) {
new (p) T(_rValue);
} //析构指定对象,但不释放内存空间
void destory(T * p) {
if (p != nullptr) {
p->~T();
}
} }; template<typename T, typename Allocate=MyAllocate<T>>
class MyVector2 { public: //构造函数
MyVector2<T, Allocate>(int size = 10, const Allocate & _allocate = MyAllocate<T>()) : allocator(_allocate) { first = allocator.allocate(size);
last = first;
end = first + size;
cout << "MyVector2 构造函数,构建数量=" << size <<"堆空间构造起始地址="<<first<<"结束地址=" << endl;
} //拷贝构造,根据指定的 MyVector2 创建新的MyVector2
MyVector2<T, Allocate>(const MyVector2<T,Allocate> & _rValue) { //1:根据原MyVector2的Size 申请内存空间
first = allocator.allocate(_rValue.Size());
last = first;
end = first + __rValue.Size(); //2:根据原MyVector2内的对象,在第1步申请的堆内存中构造对象
T *tep = _rValue.first;
while (tep<_rValue.end)
{
allocate.construct(last, *tep)
last++;
tep++;
}
cout << "MyVector2 拷贝构造函数,构建数量" << __rValue.Size() << endl;
} //赋值函数
MyVector2<T, Allocate> & operator=(const MyVector2<T, Allocate> & _rValue) {
if (this == &_rValue) { return *this;} //1:先析构目标Vector2中所有的对象
T * tep = first;
while (tep < last) {
allocator.destory(tep);
tep++;
} //2:释放目标Vector2的内存空间
allocator.delAllocate(first); //3:根据原MyVector2的size 申请新的内存空间
int rSize = _rValue.Size();
T * _head allocator.allocate(rSize);
first = _head;
last = _head;
end = first + rSize; //4:根据原MyVector2中的有效的对象在 第3步申请的内存空间上构建对象 T *tep = _rValue.first;
while (tep<_rValue.end)
{
allocator.construct(last, *tep)
last++;
tep++;
} cout << "MyVector2 赋值函数,构建数量" << rSize << endl; } //在已经申请空间的位置 添加值
void pushBack(const T & _addValue) {
if (Full()) {
Expend();//两倍扩容
}
//在指定地址空间 构建对象
allocator.construct(last, _addValue);
cout << "pushBack 元素,内存地址=" << last << endl;
last++; } //弹出
void popBack() {
if (Empty()) { return; }
//1:只析构指定位置对象,但不释放空间
allocator.destory(last - 1);
cout << "popBack元素,其内存地址=" << (last - 1) << endl; //2:移动last指针
last--; } //是否为空
bool Empty() { return first == last; } //是否满
bool Full() { return last == end; } int Size() { return end - first; } //容器扩容
void Expend() { int newSize = this->Size() * 2;//两倍扩容 //1:申请新的空间
T * head = allocator.allocate(newSize);
T * tep_first = head;
T * tep_last = head;
T * tep_end = first + newSize; cout << "两倍扩容,新的堆内存起始地址=" << head << endl; //2:将原来有效的对象 赋值到新的堆空间上
T * tep = first;
while (tep < last) {
allocator.construct(tep_first,*tep);
cout << "两倍扩容,赋值对象,原对象地址=" << tep <<"目标对象地址="<<tep_first<< endl;
tep_first++;
tep_last++;
tep++;
} tep = first;
//3:析构原堆空间上有效的对象
while (tep < last) {
allocator.destory(tep);
cout << "两倍扩容,析构对象,对象地址=" << tep << endl;
tep++; } //4:释放堆上的空间
allocator.delAllocate(first);
cout << "两倍扩容,销毁原空间" << first << endl;
first = head;
last = tep_last;
end = first + newSize; } void showVectorInfo() {
T * tep = first;
while (tep < last)
{
cout << "打印Vector中有效对象地址=" << tep << endl;
tep++;
}
} private:
T * first;
T * last;
T * end;
Allocate allocator; }; int main() { MyVector2<student, MyAllocate<student>> v(4); student s1(20, "zs1"); v.pushBack(s1); student s2(22, "zs2"); v.pushBack(s2); student s3(23, "zs3"); v.pushBack(s3); student s4(24, "zs4"); v.pushBack(s4); v.showVectorInfo(); student s5(25, "zs5"); v.pushBack(s5); v.showVectorInfo(); system("pause"); return 1;
}

<四>理解空间配置器allocator, 优化STL 中的Vector的更多相关文章

  1. C++ 空间配置器(allocator)

    C++ 空间配置器(allocator) 在STL中,Memory Allocator 处于最底层的位置,为一切的 Container 提供存储服务,是一切其他组件的基石.对于一般使用 STL 的用户 ...

  2. STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

    上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator del ...

  3. 《STL源码剖析》chapter2空间配置器allocator

    为什么不说allocator是内存配置器而说是空间配置器,因为空间不一定是内存,也可以是磁盘或其他辅助介质.是的,你可以写一个allocator,直接向硬盘取空间.sgi stl提供的配置器,配置的对 ...

  4. STL源码剖析 — 空间配置器(allocator)

    前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...

  5. C++ STL学习之 空间配置器(allocator)

    众所周知,一般情况下,一个程序包括数据结构和相应的算法,而数据结构作为存储数据的组织形式,与内存空间有着密切的联系. 在C++ STL中,空间配置器便是用来实现内存空间(一般是内存,也可以是硬盘等空间 ...

  6. STL学习笔记:空间配置器allocator

    allocator必要接口: allocator::value_type allocator::pointer allocator::const_pointer allocator::referenc ...

  7. STL源码剖析——空间配置器Allocator#1 构造与析构

    以STL的运用角度而言,空间配置器是最不需要介绍的东西,因为它扮演的是幕后的角色,隐藏在一切容器的背后默默工作.但以STL的实现角度而言,最应该首先介绍的就是空间配置器,因为这是这是容器展开一切运作的 ...

  8. STL之空间配置器allocator

    摘要 C++STL的空间配置器将内存的分配.释放,对象的构造.析构都分开执行,内存分配由alloc::allocate()负责,内存的释放由alloc::deallocate()负责:对象的构造由:: ...

  9. STL源码剖析——空间配置器Allocator#3 自由链表与内存池

    上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请.但只是对其概念进行点到为止的认识,并未深入探究.这节就来学习一下自由链表的填充和内存池的内存分配机制. refil ...

  10. [C++] 空间配置器——allocator类

    1.new和delete有一些灵活性上的局限:new把内存分配和对象构造组合在了一起:delete将对象析构和内存释放组合在了一起.   2.当分配一大块内存时,我们通常计划在这块内存上按需构造对象, ...

随机推荐

  1. KingbaseES 数据库Windows环境下注册数据库服务

    关键字: KingbaseES.Java.Register.服务注册 一.安装前准备 1.1 软件环境要求 金仓数据库管理系统KingbaseES V8.0支持微软Windows 7.Windows ...

  2. 如何使用helm优雅安装prometheus-operator,并监控k8s集群微服务

    前言:随着云原生概念盛行,对于容器.服务.节点以及集群的监控变得越来越重要.Prometheus 作为 Kubernetes 监控的事实标准,有着强大的功能和良好的生态.但是它不支持分布式,不支持数据 ...

  3. 命令行配置Windows高级防火墙

    今天正好看到个帖子,询问如何通过命令行配置防火墙策略中远程IP的地址,特别是添加新的地址. 就是图中Scope里Remote IP address的地址. 第一反应就是用netsh firewall来 ...

  4. Logstash:Grok filter 入门

    转载自: https://blog.csdn.net/UbuntuTouch/article/details/105922198 Logstash模式:https://www.elastic.co/g ...

  5. 还不会Traefik?看这篇文章就够了!

    文章转载自:https://mp.weixin.qq.com/s/ImZG0XANFOYsk9InOjQPVA 提到Traefik,有些人可能并不熟悉,但是提到Nginx,应该都耳熟能详. 暂且我们把 ...

  6. 使用scrapy爬取长安有妖气小说

    目标网站:https://www.snwx3.com/txt/434282.html 第一章地址:https://www.snwx3.com/book/434/434282/92792998.html ...

  7. 使用supervisor管理tomcat,nginx等进程详解

    1,介绍 官网:http://supervisord.org Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时 ...

  8. 编码中的Adapter,不仅是一种设计模式,更是一种架构理念与解决方案

    大家好,又见面了. 不知道下面这玩意大家有没有见过或者使用过?这是一个插座转换器.我们都知道日常使用的是220v的交流电,而国外不同国家使用的电流电压是不一样的(比如日本使用的是110v).且插座的接 ...

  9. RabbitMQ原理和架构图解(附6大工作模式)

    为什么要使用RabbitMQ? 1.解耦 系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦. 2.异步 将消息写入消息队列,非必要的业务逻辑以异步的方式运 ...

  10. LeetCode - 数组遍历

    1. 485. 最大连续 1 的个数 1.1 分析题意 首先:我们求的是连续的1的个数,所以我们不能也没必要对数组进行排序: 其次:只要求求出最大连续1的个数,并不要求具体的区间数目,所以我们只需要用 ...