您的位置:首页 > 编程语言 > C语言/C++

整理 C++ 中 Allocator 的(几乎)所有细节 1

2017-09-14 19:47 204 查看
Allocator(概念)是对访问、寻址、分配、释放、构造和析构策略的封装。是一个满足特定要求的类。标准库中需要分配释放存储空间的容器都需要一个Allocator,除了std::array。

必选成员

Allocator 需要满足的条件有很多,但是大部分都是可选的,只有几个必须存在的成员。

value_type: 要分配空间的类型

allocate(n): 分配方法

deallocate(ptr, n): 释放方法

拷贝构造:
对于表达式Alloc a2 = a1; Alloc a2(a1) 要求执行完毕后a2 == a1,不可抛出异常

移动构造:
对于表达式Alloc a2 = std::move(a1); Alloc a2(std::move(a1)); 使用a1构造a2,a2应等于a1的先前值。(C++17 起要求a1的值在构造后不发生改变,并且a1 == a2。)

拷贝赋值和移动赋值
标准没有声明他们的存在,但是显然他们应该和上面的构造语义相同。

从另一个allocator类型构造
用于map等分配的实际类型不是你传入的allocator的value_type的情况。

对于表达式 AllocA a(b),其中b是由AllocA::template rebind获得的类型AllocB的实例,构造a,使得AllocB(a) == b, b == AllocA(a)。

从另一个allocator类型移动构造
对于表达式 AllocA a(b),其中b是由AllocA::template rebind获得的类型AllocB的实例,构造a,使得a == 之前的 AllocA(b)

其它的可选设施可以在这一页找到,是否实现由 Allocator 的实现逻辑决定 http://en.cppreference.com/w/cpp/concept/Allocator

标准推荐通过 std::allocator_traits<Allocator> 来调用 Allocator 的各种方法,及获取其他类型,这个 trait 类提供了 Allocator 中可选成员的默认实现。

一些可选成员的用途

size_type

如果你不想使用默认的std::size_t,可以用这个来自定义size_type类型,当然对应的allocate和deallocate方法的size参数也应该变成这个类型

template rebind<U>::other
rebind用来从已有的allocator类型获取一个新的用来分配另一个类型U的allocator类型

注意,rebind只对有模板参数的allocator可选

allocator_traits的默认实现是用U来替换当前类型的第一个模板参数

allocate(n, ptr)
分配足够容纳n个对象的连续空间,ptr用作一个hint(比如在ptr地址附近寻找可用内存,用来保持局部性)

max_size()
获取可分配的最大对象数目

allocator_traits会提供一个返回(size_t)-1的实现(或者(size_t)-1 / sizeof(T),since C++17)

select_on_container_copy_construction()
在标准库容器拷贝构造时,由构造函数调用,向源allocator获取一个用来构造新容器的allocator的实例

allocator_traits会提供一个直接返回源容器的allocator本身的实现

在构造函数不能够满足allocator的逻辑需求时定义这个函数

construct(ptr, args…)
在给定指针指向的内存上构造对象,需要注意的是,ptr指向的对象类型不一定是allocator的value_type,这个函数有必要做成模板的

在需要自定义对象构造行为时定义它,比如打个log,try_catch一下什么的

destroy(ptr)
析构ptr指向的对象,需要注意的是,ptr指向的对象类型也不一定是value_type,这个函数有必要做成模板的

is_always_equal (since C++17)
allocator的相等比较的意义是,一个allocator分配的空间,是否可以用另外一个allocator来释放,is_always_equal旨在尽可能消除运行期的比较

std::allocator就是always_equal的,因为他们都是new和delete的封装,一个std::allocator new的当然可以用另一个std::allocator来delete

allocator_traits的默认实现是,当你的allocator是空类,那么为true_type

propagate_on_container_copy_assignment

propagate_on_container_move_assignment

propagate_on_container_swap
此三个类型标记了在容器进行拷贝赋值、移动赋值或交换的时候,allocator是否需要进行对应操作。

容器在进行拷贝赋值、移动赋值和交换时的逻辑,应该考虑到以上成员和allocator的相等性

在两个容器拷贝赋值时(container1 = container2)

propagate…copy…

两个allocator是否相等

拷贝赋值行为

true

true

拷贝allocator,拷贝container2所有元素

true

false

析构container1所有元素并释放空间,拷贝allocator,拷贝container2元素

false

true

不拷贝allocator,拷贝container2所有元素

false

false

不拷贝allocator,拷贝container2所有元素

在两个容器移动赋值时(container1 = std::move(container2))

propagate…move…

两个allocator是否相等

移动赋值行为

true

true

析构container1所有元素并释放空间,移动allocator,接管container2的内部指针

true

false

析构container1所有元素并释放空间,移动allocator,接管container2的内部指针

false

true

析构container1所有元素并释放空间,不移动allocator,接管container2的内部指针

false

false

析构container1所有元素,不释放空间,不移动allocator,分配足够装下container2所有元素的空间,将container2的元素尽数移动过来

在两个容器交换时,没有更多问题,仅需视propagate_on_container_swap值,交换allocator即可。但需要注意的是,如果allocator不可交换,并且不相等,那么容器交换是UB

当然以上只是标准容器的实现,你的容器大可以不必如此麻烦。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: