• std::allocator在stl容器中使用问题


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

  • 相关阅读:
    opencv(8)直方图操作
    opencv(9)直方图均衡化,对比,匹配
    最近没有更新日记
    dsp 链接命令文件的写法
    sqlserver 的数据库备份 还原安全操作 备忘录
    主板的各种抱错声音
    hibernate 的自动生成工具
    如何学习,如何提出问题,如何解决问题,如何脑筋急转弯
    framework 的 错误提示?
    hack 入侵 142 主机的过程
  • 原文地址:https://www.cnblogs.com/coding-my-life/p/13584824.html
Copyright © 2020-2023  润新知