allocator的使用

scorlw 发布于

allocator的使用

c++

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

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

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

1
2
3
4
5
allocator<T> a//定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存
a.allocate(n)//分配了一段原始的、未构造的内存,保存n个类型为T的对象
a.deallocate(p,n)//释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocator返回的指针,且n必须是p创建时所需要的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destory
a.construct(p, args)//p必须是一个类型为T*的指针,指向一块原始内存;arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象
a.destory(p)//p为T*类型的指针,此算法对p指向的对象执行析构函数

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

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

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

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

1
2
3
4
5
//这些函数在给定目的位置创建元素,而不是由系统分配内存给它们。
uninitialized_copy(b, e, b2)//从迭代器b和e指出的输入范围中拷贝元素到迭代器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必须指向足够大的未构造的原始内存,能够容纳给定数量的对象

在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>

namespace allocator_ {

////////////////////////////////////////////////
// reference: C++ Primer(Fifth Edition) 12.2.2
int test_allocator_1()
{
std::allocator<std::string> alloc; // 可以分配string的allocator对象
int n{ 5 };
auto const p = 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);

return 0;
}

int test_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);

return 0;
}

////////////////////////////////////////////////////////////
// reference: http://www.modernescpp.com/index.php/memory-management-with-std-allocator
int test_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;

return 0;
}

//////////////////////////////////////////////////////
// reference: http://en.cppreference.com/w/cpp/memory/allocator
int test_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);

return 0;
}

} // namespace allocator_

原文地址:https://blog.csdn.net/fengbingchun/article/details/78943527/