std::allocator常用于stl中的各种容器。对应的,stl的容器中也提供了相应的内存分配器参数。当需要统计内存的使用或者自定义内存分配时,十分有用。以std::vector为例:
// std=c++11
// https://www.cplusplus.com/reference/vector/vector/vector/
template < class T, class Alloc = allocator<T> > class vector;
explicit vector (const allocator_type& alloc = allocator_type());
explicit vector (size_type n);
vector (size_type n, const value_type& val,
const allocator_type& alloc = allocator_type());
template <class InputIterator>
vector (InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
vector (const vector& x);
vector (const vector& x, const allocator_type& alloc);
vector (vector&& x);
vector (vector&& x, const allocator_type& alloc);
vector (initializer_list<value_type> il,
const allocator_type& alloc = allocator_type());
可以看到,有两个地方可以使用分配器,一个是声明vector时的模板参数,另一个是构造vector对象时的构造参数alloc。通常我会觉得这个很简单,但是最近在项目中发现自定义的内存分配器没生效,才发现踩了一些坑。原因是有两个地方可以使用分配器,那么除去都不使用分配器的情况,则有 2 * 2 - 1 = 3 种使用情况,在一些巧合的原因下,会产生一些意想不到的结果。
#include <vector>
#include <iostream>
template <class T>
class StlAlloc : public std::allocator<T>
{
public:
using value_type = T;
using size_type = size_t;
template <class U>
struct rebind
{
using other = StlAlloc<U>;
};
public:
StlAlloc() = default;
~StlAlloc() = default;
T *allocate(size_type n, std::allocator<void>::const_pointer hint=0)
{
std::cout << __FUNCTION__ << " " << n << " " << this << std::endl;
return static_cast<T *>(operator new(sizeof(T) * n));
}
void deallocate(T *p, size_type n)
{
operator delete(p);
}
};
int main()
{
// 情景1:仅模板参数使用分配器
std::vector<int, StlAlloc<int>> v;
v.resize(1024, 0);
std::vector<int, StlAlloc<int>> v2;
v2.resize(1024, 0);
// 情景2:模板参数和构造参数均使用分配器
StlAlloc<int> alloc;
std::vector<int, StlAlloc<int>> v3(alloc);
v3.resize(1024, 0);
std::vector<int, StlAlloc<int>> v4(alloc);
v4.resize(1024, 0);
// 情景3:仅构造参数均使用分配器
std::vector<int> v5(alloc);
v5.resize(1024, 0);
std::vector<int> v6(alloc);
v6.resize(1024, 0);
return 0;
}
在线运行 结果
allocate 1024 0x77b21dc9db20
allocate 1024 0x77b21dc9db40
allocate 1024 0x77b21dc9db60
allocate 1024 0x77b21dc9db80
仅模板参数使用分配器
std::vector<int, StlAlloc<int>> v;
v.resize(1024, 0);
std::vector<int, StlAlloc<int>> v2;
v2.resize(1024, 0);
和预期的结果一致,每个对象都使用构造函数vector (const allocator_type& alloc = allocator_type())
根据模板参数allocator_type创建了一个分配器,因此打印出以下两行日志,每个分配器的地址都不一样
allocate 1024 0x77b21dc9db20
allocate 1024 0x77b21dc9db40
模板参数和构造参数均使用分配器
StlAlloc<int> alloc;
std::vector<int, StlAlloc<int>> v3(alloc);
v3.resize(1024, 0);
std::vector<int, StlAlloc<int>> v4(alloc);
v4.resize(1024, 0);
一直以为,当在构造参数传入分配器时,vector会使用此分配器,而不再额外创建分配器。然而,从日志来看
allocate 1024 0x77b21dc9db60
allocate 1024 0x77b21dc9db80
分配器的地址是不一样的。根据www.cplusplus.com的描述
alloc
Allocator object.
The container keeps and uses an internal copy of this allocator.
即使传入了分配器,也会执行拷贝。而一般来说,自定义的内存分配器都是希望多个对象共用同一个内存分配器的,这样内存利用率高,这就需要额外处理了,比如说在allocate函数里调用全局的内存池。
仅构造参数均使用分配器
std::vector<int> v5(alloc);
v5.resize(1024, 0);
std::vector<int> v6(alloc);
v6.resize(1024, 0);
这其实是一种错误的用法,一般不会这样写。之所以说这个用例是因为项目中的旧代码改漏了,结果发现内存统计的时候完全没统计到对应的内存分配,而编译运行却没有问题,排查后才发现问题。默认情况下,stl的容器使用std::allocator分配内存,上面的例子中,因为继承了std::allocator,所以传入的alloc被转换为基类std::allocator,而且会执行一份拷贝,那最终得到的分配器类型就是std::allocator所以没有任何日志输出,也没有报错。
把例子中的class StlAlloc改成不继承std::allocator就会因为传入的参数和声明时分配器的参数不一致编译报错。