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就会因为传入的参数和声明时分配器的参数不一致编译报错。

posted on 2020-08-30 11:46  coding my life  阅读(928)  评论(0编辑  收藏  举报