当前位置: 首页 > news >正文

C++中std::allocator的使用案例详解

标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。使用allocator通常会提供更好的性能和更灵活的内存管理能力。

new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起。类似的,delete将对象析构和内存释放组合在了一起。我们分配单个对象时,通常希望将内存分配和对象初始化组合在一起。因为在这种情况下,我们几乎肯定知道对象应有什么值。当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在此情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才真正执行对象的创建操作(同时付出一定开销)。一般情况下,将内存分配和对象构造组合在一起可能会导致不必要的浪费

标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。类似vector,allocator是一个模板。为了定义一个allocator对象,我们必须指明这个allocator可以分配的对象类型。当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置。allocator支持的操作,如下:

allocatro分配的内存是未构造的(unconstructed)。我们按需要在此内存中构造对象。在新标准库中,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象。类似make_shared的参数,这些额外参数必须是与构造的对象的类型相匹配的合法的初始化器。

在早期版本的标准库中,construct只接受两个参数:指向创建对象位置的指针和一个元素类型的值。因此,我们只能将一个元素拷贝到未构造空间中,而不能用元素类型的任何其它构造函数来构造一个元素。还未构造对象的情况下就使用原始内存是错误的。为了使用allocator返回的内存,我们必须用construct构造对象。使用未构造的内存,其行为是未定义的。

当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。函数destroy接受一个指针,对执行的对象执行析构函数。我们只能对真正构造了的元素进行destroy操作。一旦元素被销毁后,就可以重新使用这部分内存来保存其它string,也可以将其归还给系统。释放内存通过调用deallocate来完成。我们传递给deallocate的指针不能为空,它必须指向由allocate分配的内存。而且,传递给deallocate的大小参数必须与调用allocate分配内存时提供的大小参数具有一样的值。

标准库还为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象。它们都定义在头文件memory中,如下:

在C++中,内存是通过new表达式分配,通过delete表达式释放的。标准库还定义了一个allocator类来分配动态内存块。分配动态内存的程序应负责释放它所分配的内存。内存的正确释放是非常容易出错的地方:要么内存永远不会被释放,要么在仍有指针引用它时就被释放了。新的标准库定义了智能指针类型------shared_ptr、unique_ptr和weak_ptr,可令动态内存管理更为安全。对于一块内存,当没有任何用户使用它时,智能指针会自动释放它。现代C++程序应尽可能使用智能指针。

std::allocator是标准库容器的默认内存分配器。你可以替换自己的分配器,这允许你控制标准容器分配内存的方式。

以上内容主要摘自:《C++Primer(Fifth Edition 中文版)》第12.2.2章节

下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

#include "allocator.hpp"

#include <iostream>

#include <memory>

#include <string>

#include <vector>

namespaceallocator_ {

// reference: C++ Primer(Fifth Edition) 12.2.2

inttest_allocator_1()

{

std::allocator<std::string> alloc;// 可以分配string的allocator对象

intn{ 5 };

autoconstp = alloc.allocate(n);// 分配n个未初始化的string

auto q = p;// q指向最后构造的元素之后的位置

alloc.construct(q++);// *q为空字符串

alloc.construct(q++, 10,'c');// *q为cccccccccc

alloc.construct(q++,"hi");// *q为hi

std::cout << *p << std::endl;// 正确:使用string的输出运算符

//std::cout << *q << std::endl; // 灾难:q指向未构造的内存

std::cout << p[0] << std::endl;

std::cout << p[1] << std::endl;

std::cout << p[2] << std::endl;

while(q != p) {

alloc.destroy(--q);// 释放我们真正构造的string

}

alloc.deallocate(p, n);

return0;

}

inttest_allocator_2()

{

std::vector<int> vi{ 1, 2, 3, 4, 5 };

// 分配比vi中元素所占用空间大一倍的动态内存

std::allocator<int> alloc;

auto p = alloc.allocate(vi.size() * 2);

// 通过拷贝vi中的元素来构造从p开始的元素

/* 类似拷贝算法,uninitialized_copy接受三个迭代器参数。前两个表示输入序列,第三个表示

这些元素将要拷贝到的目的空间。传递给uninitialized_copy的目的位置迭代器必须指向未构造的

内存。与copy不同,uninitialized_copy在给定目的位置构造元素。

类似copy,uninitialized_copy返回(递增后的)目的位置迭代器。因此,一次uninitialized_copy调用

会返回一个指针,指向最后一个构造的元素之后的位置。

*/

auto q = std::uninitialized_copy(vi.begin(), vi.end(), p);

// 将剩余元素初始化为42

std::uninitialized_fill_n(q, vi.size(), 42);

return0;

}

// reference: http://www.modernescpp.com/index.php/memory-management-with-std-allocator

inttest_allocator_3()

{

std::cout << std::endl;

std::allocator<int> intAlloc;

std::cout <<"intAlloc.max_size(): "<< intAlloc.max_size() << std::endl;

int* intArray = intAlloc.allocate(100);

std::cout <<"intArray[4]: "<< intArray[4] << std::endl;

intArray[4] = 2011;

std::cout <<"intArray[4]: "<< intArray[4] << std::endl;

intAlloc.deallocate(intArray, 100);

std::cout << std::endl;

std::allocator<double> doubleAlloc;

std::cout <<"doubleAlloc.max_size(): "<< doubleAlloc.max_size() << std::endl;

std::cout << std::endl;

std::allocator<std::string> stringAlloc;

std::cout <<"stringAlloc.max_size(): "<< stringAlloc.max_size() << std::endl;

std::string* myString = stringAlloc.allocate(3);

stringAlloc.construct(myString,"Hello");

stringAlloc.construct(myString + 1,"World");

stringAlloc.construct(myString + 2,"!");

std::cout << myString[0] <<" "<< myString[1] <<" "<< myString[2] << std::endl;

stringAlloc.destroy(myString);

stringAlloc.destroy(myString + 1);

stringAlloc.destroy(myString + 2);

stringAlloc.deallocate(myString, 3);

std::cout << std::endl;

return0;

}

//

// reference: http://en.cppreference.com/w/cpp/memory/allocator

inttest_allocator_4()

{

std::allocator<int> a1;// default allocator for ints

int* a = a1.allocate(1);// space for one int

a1.construct(a, 7);// construct the int

std::cout << a[0] <<'\n';

a1.deallocate(a, 1);// deallocate space for one int

// default allocator for strings

std::allocator<std::string> a2;

// same, but obtained by rebinding from the type of a1

decltype(a1)::rebind<std::string>::other a2_1;

// same, but obtained by rebinding from the type of a1 via allocator_traits

std::allocator_traits<decltype(a1)>::rebind_alloc<std::string> a2_2;

std::string* s = a2.allocate(2);// space for 2 strings

a2.construct(s,"foo");

a2.construct(s + 1,"bar");

std::cout << s[0] <<' '<< s[1] <<'\n';

a2.destroy(s);

a2.destroy(s + 1);

a2.deallocate(s, 2);

return0;

}

}// namespace allocator_

http://www.jsqmd.com/news/901679/

相关文章:

  • 5. 问:某个方案写:“将用户问题先做意图分类,再路由到不同 Prompt 模板。”指出其中的一个隐性危险,并说明什么场景下危险会被放大到不可接受。
  • SystemC与FMI集成框架在嵌入式系统开发中的应用
  • 别再混淆了!一文讲透嵌入式中间件与互联网中台的核心差异(附基站中间件实战API解析)
  • AI代码安全审计实战:从Claude生成代码的漏洞挖掘到安全集成策略
  • Cortex-M处理器模式检测与调试技巧详解
  • 后端技术栈与数据库优化:提升系统整体性能
  • ACC自适应巡航控制 软件使用:Carsim2019.0+Matlab_Simulink2021a 适用场景:采用模块化建模方法,搭建ACC自适应巡航控制系统,适用于弯道和直线行驶场景。
  • 选择命理推演软件,到底该看什么?
  • Java老兵的逆袭:手把手教你从后端工程师转型AI应用架构师,高薪收藏必备!
  • 告别混乱地址:手把手教你用OData增强定制SAP标准发票的“Bill to”信息
  • 别再到处找教程了!Windows 10/11 保姆级 Mosquitto MQTT 服务器搭建(含MQTTX客户端连接测试)
  • 告别期刊投稿内耗!okbiye 期刊论文 AI 助手,从普刊到 SCI 一键搞定
  • 别再只会点Merge了!IntelliJ IDEA里用Rebase优雅解决Git冲突的完整流程
  • 别再手动调增益了!手把手教你用RFSoC的AGC功能搞定动态信号(附Vivado 2023.1工程)
  • 后端开发新手入门:快速上手必备技能与工具
  • 从相似性分数到自注意力:Transformer核心机制详解与实战
  • 别再被“AI中医大模型”骗了!苹果应用商店能下载的,我帮你筛出了这12款
  • 「开源」四路鱼眼相机360°全景环视系统——从标定到拼接全流程(源码+教程)
  • 从游戏图形到AI芯片:浮点数格式FP32/FP16/FP8的演进史与硬件设计启示
  • 从Vibe Check到科学评估:构建AI模型可量化评估体系的实践指南
  • 如何高效获取Zenodo科研数据:专业开发者的完整解决方案
  • 保姆级教程:手把手教你下载、解析与可视化ScanNet RGB-D数据集(附Python代码)
  • YOLOv8n-Ghost优化与FPGA加速在SAR船舶检测中的应用
  • 移动端GPU内存告急?手把手教你为Unity/UE4手游项目选对纹理压缩格式(ASTC vs ETC2实战解析)
  • 2026杭州工装:为什么新锐公司更适配企业装修需求
  • Keil MDK与Arm DS在Cortex-R开发中的对比与选型
  • n8n与Claude集成:开发者如何构建智能工作流自动化解决方案
  • Keil uVision彻底卸载指南:解决残留问题与注册表清理
  • 告别死记硬背:用‘生产者-消费者’模型图解LwIP的tcpip_thread与邮箱机制
  • 别再给主力机装SQL Server了!用群晖Docker搭个2019版,开发测试两不误