1. 必须要注意的 C++ 动态内存资源管理(六)——vector的简单实现
  2.  
  3. 十六.myVector分析
  4.         我们知道,vector类将其元素存放在连续的内存中。为了获得可接受的性能,vetor预先分配足够大的内存来保存可能需要的更多元素。vector的每个添加元素的成员函数会检查是否有空间容纳更多的元素。如果有,成员函数会在下一个可用位置构造一个对象。如果没有可用空间,vector就会重新分配空间;它获得新的空间,将已有元素移动到新空间中,释放旧空间,并添加新元素。         既然是动态开辟的内存,于是我们在myVector中使用动态数组来存储,而每次插入元素的时候需要先判断开辟的内存是否已满,如果满了需要重新分配内存。
  5.         下面给出最初版本的代码:
  6.  
  7. //myVector.h
  8. #include <memory>
  9.  
  10. template<typename T>
  11. class myVector
  12. {
  13. public:
  14. typedef myVector<T> _Myt;
  15. myVector() :
  16. elements(nullptr), first_free(nullptr), cap(nullptr){} // allocator成员进行默认初始化
  17. myVector(const _Myt&);
  18. _Myt& operator=(const _Myt&);
  19. ~myVector();
  20. T& operator[](size_t i){ return *(elements + i); }
  21. void push_back(const T&); // 添加元素
  22. size_t size()const{ return first_free - elements; }
  23. size_t capacity()const{ return cap - elements; }
  24. T *begin()const{ return elements; }
  25. T *end()const{ return first_free; }
  26. private:
  27. void chk_n() //被添加元素函数使用
  28. {
  29. if (size() == capacity())reallocate();
  30. }
  31. std::pair<T*, T*> n_copy
  32. (const T*, const T*); //被拷贝构造,赋值运算符,析构函数使用
  33. void free(); //销毁元素并释放内存
  34. void reallocate(); //获得更多内存并拷贝已有元素
  35.  
  36. T *elements;
  37. T *first_free;
  38. T *cap;
  39. };
  40.  
  41. template<typename T>
  42. myVector<T>::myVector(const _Myt& v)
  43. {
  44. //调用alloc_n_copy 分配空间以容纳与s中一样多的元素
  45. auto newdata = n_copy(v.begin(), v.end());
  46. elements = newdata.first;
  47. first_free = cap = newdata.second;
  48. }
  49. template<typename T>
  50. myVector<T>::~myVector()
  51. {
  52. free();
  53. }
  54. template<typename T>
  55. myVector<T>& myVector<T>::operator=(const _Myt& rhs)
  56. {
  57. //调用alloc_n_copy分配内存,大小与rhs一样.
  58. auto data = n_copy(rhs.begin(), rhs.end());
  59.  
  60. free();
  61.  
  62. elements = data.first;
  63. first_free = cap = data.second;
  64.  
  65. return *this;
  66. }
  67. template<typename T>
  68. std::pair<T*, T*> myVector<T>::
  69. n_copy(const T *b, const T *e)
  70. {
  71. auto data = new T[e - b];
  72. for (auto i = b; i < e; i++)
  73. data[i-b] = *i;
  74.  
  75. return{ data, data + (e - b) };
  76. }
  77. template<typename T>
  78. void myVector<T>::push_back(const T& s)
  79. {
  80. chk_n(); //确保已有新空间
  81. *(first_free++) = s;
  82. }
  83.  
  84. template<typename T>
  85. void myVector<T>::free()
  86. {
  87. delete[] elements;
  88. }
  89.  
  90. template<typename T>
  91. void myVector<T>::reallocate()
  92. {
  93. //我们将分配当前大小两倍的内存空间
  94. auto newcapacity = size() ? * size() : ;
  95.  
  96. //分配新内存
  97. auto newdata = new T[newcapacity];
  98. auto dest = newdata;
  99. auto elem = elements;
  100.  
  101. //将数据从旧地址移动到新地址
  102. for (size_t i = ; i != size(); ++i)
  103. *(dest++) = *(elem++);
  104. free(); //一旦更新完就要释放旧内存
  105.  
  106. elements = newdata;
  107. first_free = dest;
  108.  
  109. cap = elements + newcapacity;
  110. }
  111.  
  112.         恩,以上代码实现了vector的部分功能,实现了vector内存的动态分配。不过,我们可以发现以上代码还是有几个可以优化的地方:
  113. .在分配内存的时候,new将内存分配和对象构造组合在了一起。但是在vector分配内存的时候,事实上有许多内存我们可能并用不上;而如果对于这些内存进行构造对象,可能就会带来不必要的开销。
  114. .在内存重新分配的时候,我们涉及到了旧数据的转移;不对,这里应该说是拷贝。虽然的确应该是转移,然而我们实现是通过拷贝。在c++11的时候提供了移动构造语义。它可以对于即将销毁(保证重新赋值前不再使用)的对象进行移动。这样对于某些支持移动语义的对象。移动比拷贝就可以带来更小的开销。
  115.         恩,要进行以上的优化我们要先介绍:allocator类,移动语义。
  116.  
  117. 十七.allocator类介绍
  118.         new有一些灵活性的局限,一方面表现在它将内存分配和对象构造组合在一起。类似的,delete将对象析构和内存释放组合在一起。我们分配单个对象时,通常我们希望将内存和对象初始化放在一起。因为在这样的情况下,我们几乎已经知道了对象应当是什么值。
  119.         当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在这种情况下,我们就应该希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,然而只有我们真正需要的时候才执行对象创建操作(同时付出一定开销)。
  120.         标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来。
  121.  
  122. 函数 介绍
  123. allocator<T> a 定义了一个名为aallocator对象,它可以为类型为T的对象分配内存。
  124. a.allocate(n) 分配一段原始的,未构造的内存,保存n个类型为T的对象。
  125. a.deallocate(p,n) 释放从T*指针p开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针,且n必须是p创建时指定的大小。在调用deallocate之前,用户必须对每个在这块内存创建的内存调用destroy
  126. a.construct(p,args) p必须是一个类型为T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存上构造一个对象。
  127. a.destroy(p) pT*类型指针,此算法对p指向的对象执行析构函数。
  128.         下面是一段简单的代码,介绍了如何使用allocator进行内存分配,对象构造,对象释放,内存回收。
  129. int main()
  130. {
  131. allocator<string> alloc; //这个对象可以用来分配 string 类型的内存。
  132. string* p = alloc.allocate(); //使用alloc对象分配5个string对象大的连续内存并将头指针给p。
  133.  
  134. //allocate分配的内存在没有构造之前是不能使用的!!
  135. alloc.construct(p,"hello world"); //使用"hello world"构造string
  136.  
  137. cout << *p << endl; //输出刚才构造的string,输出hello world
  138.  
  139. alloc.destroy(p); //销毁刚才构造的对象
  140.  
  141. alloc.deallocate(p,); //释放内存
  142. return ;
  143. }
  144.  
  145.         不仅这样,标准库还提供了一些算法让我们使用的时候更加方便: |函数|介绍| |—|—-| |uninitialized_copy(b,e,b2)|从迭代器be指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能容纳输入序列中元素的拷贝。 |uninitialized_copy_n(b,n,b2)|从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中。 |uninitialized_fill(b,e,t)|在迭代器b,e指定的原始内存范围中创建对象,对象的值均为t的拷贝。 |uninitialized_fill_n(b,n,t)|从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造原始内存,能够容纳给定数量的对象。
  146.         值得注意的是,所有通过allocate分配的内存都必须通过deallocate去回收,而所有构造的对象都必须通过destroy去释放。所以这些拷贝算法都必须要求原始内存,如果内存上有对象,请先使用destroy释放!!
  147.  
  148. 十八.移动语义介绍
  149.         很多情况下都会发生对象拷贝,然而在其中某些情况下,对象拷贝后就会立即被销毁。在这些情况下,移动而非拷贝对象会大幅度提升性能。还有的情况诸如IO类或者unique_ptr这些类都包含不能共享的资源,因此这些类的对象也不能拷贝只能移动。         为了支持移动操作,在新标准中引入了一种新的引用类型 —— 右值引用。右值引用有个重要的性质:只能绑定到一个即将要销毁的对象上。因此我们可以自由地将一个右值引用的资源”移动”到另一个对象中。下面给出一些例子来表示哪些是右值:
  150. int i = ;
  151. int &r = i; //正确:r引用i
  152. int &&rr = i; //错误:不能将一个右值引用绑定到左值上
  153. int &r2 = i * ; //错误:i * 42 是个右值
  154. const int & r3 = i * ; //正确:我们可以把一个const引用绑定到右值上
  155. int &&rr2 = i * ; //正确:右值引用绑定右值
  156.  
  157.         考察左值和右值的区别:左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中或者是函数返回的时候创建的临时变量。
  158.         因为变量是左值,所以我们不能将一个右值引用绑定到一个变量上,即使这个变量是一个右值引用类型也不行。所以为了解决这个问题,标准库提供了一个move函数,它可以用来获得绑定到左值上的右值引用。此函数定义在头文件utility中。
  159.         我们可以销毁一个移后源对象,也可以赋予新值,但不能在赋新值之前使用移后源对象的值。
  160.         根据以上所说,如果类也支持移动拷贝和移动赋值,那么也能在某些时候的初始化(赋值)的时候提高性能。如果要想让类支持移动语义,我们需要为其定义移动构造函数和移动赋值运算符。这两个函数的参数都是一个右值引用。就如同上面的代码,对于vector的移动我们只需要拷贝三个指针参数,而不是拷贝三个指针参数指向的值。
  161.  
  162. template<typename T>
  163. myVector<T>::myVector(_Myt&& v):elements(v.elements),first_free(v.first_free),cap(v.cap){
  164. v.elements = v.first_free = v.cap = nullptr;
  165. }
  166.  
  167.         值得注意的是,我们要保证移后源对象必须是可析构状态,而且如果移动构造(赋值)函数不抛出异常的话必须要标记为noexcept(primer p474)。         对于移动赋值运算符我们要保证能正确处理自我赋值:
  168. template<typename T>
  169. myVector<T>& myVector<T>::operator=(_Myt&& rhs)
  170. {
  171. if (this != &rhs)
  172. {
  173. free();
  174. elements = rhs.elements;
  175. first_free = rhs.first_free;
  176. cap = rhs.cap;
  177. //将rhs置为可析构状态
  178. rhs.elements = rhs.first_free = rhs.cap = nullptr;
  179. }
  180. }
  181.  
  182.         当然,和其他构造函数一样,如果我们没有定义移动构造函数的时候,编译器会给我们提供默认的移动构造函数。不过,前提是该类没有定义任何版本的拷贝控制函数以及每个非staitc成员变量都可以移动。编译器就会默认为它合成移动构造函数和移动赋值运算符。
  183.  
  184. 十九.优化过后的Vector
  185.         我们使用 allocate 和移动语义对以上的vector进行优化:
  186. #pragma once
  187.  
  188. #include <memory>
  189.  
  190. template<typename T>
  191. class myVector
  192. {
  193. public:
  194. typedef myVector<T> _Myt;
  195. myVector() :
  196. elements(nullptr), first_free(nullptr), cap(nullptr){} // allocator成员进行默认初始化
  197. myVector(const _Myt&);
  198. myVector(_Myt&&);
  199. _Myt& operator=(const _Myt&);
  200. _Myt& operator=(_Myt&&);
  201. ~myVector();
  202. T& operator[](size_t i){ return *(elements + i); }
  203. void push_back(const T&); // 添加元素
  204. size_t size()const{ return first_free - elements; }
  205. size_t capacity()const{ return cap - elements; }
  206. T *begin()const{ return elements; }
  207. T *end()const{ return first_free; }
  208. private:
  209. static std::allocator<T> alloc;
  210. void chk_n_alloc() //被添加元素函数使用
  211. {
  212. if (size() == capacity())reallocate();
  213. }
  214. std::pair<T*, T*> alloc_n_copy
  215. (const T*, const T*); //被拷贝构造,赋值运算符,析构函数使用
  216. void free(); //销毁元素并释放内存
  217. void reallocate(); //获得更多内存并拷贝已有元素
  218.  
  219. T *elements;
  220. T *first_free;
  221. T *cap;
  222. };
  223.  
  224. template<typename T>
  225. std::allocator<T> myVector<T>::alloc;
  226.  
  227. template<typename T>
  228. myVector<T>::myVector(const _Myt& v)
  229. {
  230. //调用alloc_n_copy 分配空间以容纳与s中一样多的元素
  231. auto newdata = alloc_n_copy(v.begin(), v.end());
  232. elements = newdata.first;
  233. first_free = cap = newdata.second;
  234. }
  235. template<typename T>
  236. myVector<T>::myVector(_Myt&& v):elements(v.elements),first_free(v.first_free),cap(v.cap){
  237. v.elements = v.first_free = v.cap = nullptr;
  238. }
  239.  
  240. template<typename T>
  241. myVector<T>::~myVector()
  242. {
  243. free();
  244. }
  245. template<typename T>
  246. myVector<T>& myVector<T>::operator=(const _Myt& rhs)
  247. {
  248. //调用alloc_n_copy分配内存,大小与rhs一样.
  249. auto data = alloc_n_copy(rhs.begin(), rhs.end());
  250.  
  251. free();
  252.  
  253. elements = data.first;
  254. first_free = cap = data.second;
  255.  
  256. return *this;
  257. }
  258. template<typename T>
  259. myVector<T>& myVector<T>::operator=(_Myt&& rhs)
  260. {
  261. if (this != &rhs)
  262. {
  263. free();
  264. elements = rhs.elements;
  265. first_free = rhs.first_free;
  266. cap = rhs.cap;
  267. //将rhs置为可析构状态
  268. rhs.elements = rhs.first_free = rhs.cap = nullptr;
  269. }
  270. }
  271.  
  272. template<typename T>
  273. std::pair<T*, T*> myVector<T>::
  274. alloc_n_copy(const T *b, const T *e)
  275. {
  276. auto data = alloc.allocate(e - b);
  277.  
  278. //初始化并返回一个pair,该pair由data和uninitialized_copy组成
  279. return{ data, uninitialized_copy(b, e, data) };
  280. }
  281. template<typename T>
  282. void myVector<T>::push_back(const T& s)
  283. {
  284. chk_n_alloc(); //确保已有新空间
  285. alloc.construct(first_free++, s);
  286. }
  287.  
  288. template<typename T>
  289. void myVector<T>::free()
  290. {
  291. //不能传递给deallocate一个空指针,如果elements为NULL,那么函数什么都不做
  292. if (elements)
  293. {
  294. //逆序销毁所有元素
  295. for (auto p = first_free; p != elements;/* 空 */)
  296. alloc.destroy(--p);
  297. alloc.deallocate(elements, cap - elements);
  298. }
  299. }
  300.  
  301. template<typename T>
  302. void myVector<T>::reallocate()
  303. {
  304. //我们将分配当前大小两倍的内存空间
  305. auto newcapacity = size() ? * size() : ;
  306.  
  307. //分配新内存
  308. auto newdata = alloc.allocate(newcapacity);
  309.  
  310. //将数据从旧地址移动到新地址
  311. auto dest = newdata;
  312. auto elem = elements;
  313.  
  314. for (size_t i = ; i != size(); ++i)
  315. alloc.construct(dest++, std::move(*elem++));
  316. free(); //一旦更新完就要释放旧内存
  317.  
  318. elements = newdata;
  319. first_free = dest;
  320.  
  321. cap = elements + newcapacity;
  322. }
  323.  
  324.         尽管,以上的代码vector只实现了vector很少的一部分功能,而且可能实现方式也有不足的地方。不过,在这里只是想体现动态内存的使用。所以,以上的代码还是可以作为c++动态内存管理的的示例的。
  325.  
  326. 基本上c++动态内存管理的就介绍到这里了。

必须要注意的 C++ 动态内存资源管理(六)——vector的简单实现的更多相关文章

  1. 必须要注意的 C++ 动态内存资源管理(二)——指针对象简单实现

    必须要注意的 C++动态内存资源管理(二)——指针对象简单实现 四.拷贝类型的资源         上节我们说过,对于图片类型的资源我们有时候往往采用拷贝(如果对于那种公共图片,可能采用唯一副本,提供 ...

  2. 必须要注意的 C++ 动态内存资源管理(一)——视资源为对象

    必须要注意的 C++ 动态内存资源管理(一)——视资源为对象 一.前言         所谓资源就是,一旦你用了它,将来必须还给系统.如果不这样,糟糕的事情就会发生.C++ 程序中最常见使用的资源就是 ...

  3. 必须要注意的 C++ 动态内存资源管理(五)——智能指针陷阱

    必须要注意的 C++ 动态内存资源管理(五)——智能指针陷阱 十三.小心使用智能指针.         在前面几节已经很详细了介绍了智能指针适用方式.看起来,似乎智能指针很强大,能够很方便很安全的管理 ...

  4. FreeRTOS 动态内存管理

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面 ...

  5. 【STM32H7教程】第27章 STM32H7的TCM,SRAM等五块内存的动态内存分配实现

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第27章       STM32H7的TCM,SRAM等五块内 ...

  6. 【转】Linux C动态内存泄漏追踪方法

    原文:http://www.cnblogs.com/san-fu-su/p/5737984.html C里面没有垃圾回收机制,有时候你申请了动态内存却忘记释放,这就尴尬了(你的程序扮演了强盗角色,有借 ...

  7. C++指针和动态内存分配

    指针和动态内存分配 数组与指针 数组 数组名是一个指针常量. 数组名传递数据时,传递的是地址. 数组作为函数参数时不指定第一维大小. 对象数组 A a[2] = {A(1,2)}; 执行时先调用有参数 ...

  8. SQLite剖析之动态内存分配

    SQLite通过动态内存分配来获取各种对象(例如数据库连接和SQL预处理语句)所需内存.建立数据库文件的内存Cache.保存查询结果. 1.特性    SQLite内核和它的内存分配子系统提供以下特性 ...

  9. C和指针 第十一章 动态内存分配

    声明数组时,必须指定数组长度,才可以编译,但是如果需要在运行时,指定数组的长度的话,那么就需要动态的分配内存. C函数库stdlib.h提供了两个函数,malloc和free,分别用于执行动态内存分配 ...

随机推荐

  1. Java使用JsonPatch

    老规矩,概念的东西不再此处体现,baidu即可自行解决,直入主题,动手第一. 导入所需的jar文件 pom.xml     <dependencies>        <depend ...

  2. QQ推广工具

    目前比较简单易用的QQ推广工具有:一键加群.在线聊天 一.一键加群 1.官网链接 http://qun.qq.com/join.html 2.使用 1登录自己的QQ 2创建一个想要作为推广的群 3选择 ...

  3. angularcli 第八篇(router 路由)

    更多详细:https://segmentfault.com/a/1190000009265310 一.标题:页面有两个按钮(test1.test2),点击这两个按钮,跳转相应页面~ 注:可直接创建一个 ...

  4. MySQL用户与权限

    用户连接到mysql,并做各种查询,在用户和服务器中间分为两个阶段: 1:用户是否有权连接上来 2:用户是否有权执行此操作(如select,update等等) 先看第一个阶段:服务器如何判断用户是否有 ...

  5. 外部服务发现-ingress

    Ingress`其实就是从 kuberenets 集群外部访问集群的一个入口,将外部的请求转发到集群内不同的 Service 上,其实就相当于 nginx.haproxy 等负载均衡代理服务器,Ing ...

  6. 使用kubeadm部署k8s

    k8s组件 master,node master中包括apiserver,scheduler,controller.etcd apiserver:负责接收用户请求,并且保存至etcd中. schedu ...

  7. RS232、RS485和TTL电平与串行通信

    RS232.RS485和TTL 作为一个底层软件开发工程师,经常会碰到RS232.RS485和TTL这一类的问题. 之前总是碰到问题之后Google一下,把当下的问题解决了之后就不管了,过个一两天就忘 ...

  8. Vue 路由守卫解决页面退出和弹窗的显示冲突

    在使用UI框架提供的弹出层Popup时,如Vant UI的popup,在弹出层显示时,点击物理按键或者小程序自带的返回时,会直接退出页面,这明显不符合页面逻辑. 解决思路: 在弹出层显示时,点击了返回 ...

  9. 记一次用pip安装docker-compose报错及解决方法

    Docker-Compose 的安装 方法一 # 下载1.25.0 docker compose sudo curl -L "https://github.com/docker/compos ...

  10. python开发笔记-变长字典Series的使用

    Series的基本特征: 1.类似一维数组的对象 2.由数据和索引组成 import pandas as pd >>> aSer=pd.Series([1,2.0,'a']) > ...