《Hands-On System Programming with C++》读书笔记之九
new()无法对C++ STL的对象的内存操作进行管理(如std::list和std::map)。为此,C++又提出了新的概念叫做allocator,allocator类可以对指定的类型定义内存如何分配和释放。
C++ allocator介绍
allocator是C++中的一个类模板,它用于对指定的数据类型进行内存分配管理。有两种不同的allocator:
- equal allocator
- unequal allocator
当可以用一个allocator分配内存,而用另一个来释放这片内存时,称它们为equal allocator,此时 == 操作符返回值为真。例如
myallocator<myclass> myalloc1;
myallocator<myclass> myalloc2;
auto ptr = myalloc1.allocate(1);
myalloc2.deallocate(ptr, 1);
上面的两个allocator是equal allocator。否则称为unequal allocator,unequal allocator通常保存自己的状态(stateful),防止其它状态不同的allocator来释放内存。
基本allocator
最基本的allocator是无状态、等效的allocator(stateless,equal allocator)。下面就是这样一个自定义的例子。
template<typename T>
class myallocator
{
public:
using value_type = T;
using pointer = T*;
using size_type = std::size_t;
public:
myallocator() = default;
template<typename U>
myallocator(const myallocator<U> &other) noexcept
{
(void)other;
}
pointer allocate(size_type n)
{
if (auto ptr = static_cast<pointer>(malloc(sizeof(T) * n)))
{
return ptr;
}
throw std::bad_alloc();
}
void deallocate(pointer p, size_type n)
{
(void)n;
return free(p);
}
};
template<typename T1, typename T2>
bool operator == (const myallocator<T1>&, const myallocator<T2>&)
{
return true;
}
template<typename T1, typename T2>
bool operator != (const myallocator<T1>&, const myallocator<T2>&)
{
return false;
}
上面同样是一个模板类,至少要提供一个模板数据类型,这个类型是用于分配内存的对象。同时,allocator必须有一个默认的构造函数,因为C++的容器类会调用指定给它的allocator,这就要求allocator能够在不需要任何参数的条件下执行构造函数。
allocate()函数的输入输格式出是固定的,否则它就无法被已有的容器类正常调用。分配的内存通过static_cast(编译时类型检查)转换为指定数据类型的指针。这里用的是malloc()而不是new(),因为容器类会执行构造工作,allocator只需分配内存,不负责对象的构造。
deallocate()同样原因使用了free()。注意由于我们在创造一个equal allocator,所以这里的ptr不必须是原来的直接分配内存的得到指针。而参数n必须要与原来分配时一致。
类定义中出现了copy constructor。当我们为容器指定allocator时,我们是这样使用的std::list<myclass, myallocator> mylist。std::list类在为自己的节点创建对象时会拷贝myclass{}对象,为了在这个过程中避免shadow copy(只拷贝了指针而没有拷贝内存),需要调用myclass{}的copy constructor。所以在allocator类中必须使用copy constructor。 例子中的*(void)other只是指出other参数不使用,编译器不必警告。
关于 == 和!=* 操作符,由于定义的时equal allocator,allocator()和deallocate()背后的操作只是malloc()和free(),满足“当可以用一个allocator分配内存,而用另一个来释放这片内存”这个条件,因此 *==操作符返回真,!=*返回假。
理解allocator的属性和可选项
属性
返回指针
记住allocator的返回值是指针,可以使用 * 和 -> 对返回的指针进行操作。
等效性
如果对同样数据类型的allocator,==返回真,那么可以用任意一个来自由地释放对象。大部分条件下stateful allocator不是等效的,stateless allocator是等效的,但也不总是这样。
在C++17以前,容器很难判断allocator是否具有等效性,除非真的例化两个allocator的对象再比较它们。因此总是默认假设allocator没有等效性,由此失去了很多优化的空间。在C++17中,通过定义using is_always_equal = std::true_type,容器可以判断allocator具有等效性。
不同的内存分配类型
不同的容器需要不同的内存分配,因此allocator需要支持多种的内存分配类型,包括
- 每次分配的内存要是一片连续空间
- 一次可以分配多个对象的内存空间
#include <iostream>
#include <list>
template<typename T>
class myallocator
{
public:
using value_type = T;
using pointer = T*;
using size_type = std::size_t;
using is_always_equal = std::true_type;
public:
myallocator()
{
std::cout <<