C++ 全栈知识体系C++ 全栈知识体系
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
  • 编程语言与技术

    • Effective C++: 改善程序与设计的55个具体做法

      • 第2章 - 构造/析构/赋值运算(一)
      • 第2章 - 构造/析构/赋值运算(二)
      • 第2章 - 构造/析构/赋值运算(三)
      • 第3章 - 资源管理
      • 第4章 - 设计与声明(一)
      • 第4章 - 设计与声明(二)
      • 第5章 - 实现(一)
      • 第5章 - 实现(二)
      • 第6章 - 继承与面向对象设计
      • 第7章 - 模板与泛型编程
    • 深度探索C++对象模型

      • 第1章 - 关于对象
      • 第2章 - 构造函数语意学
      • 第3章 - Data 语意学
    • STL源码剖析

      • 第1章 - STL概论和版本简介
      • 第2章 - 空间配置器
      • 第3章 - 迭代器(iterators)概念与traits编程技法(一)
      • 第3章 - 迭代器(iterators)概念与traits编程技法(二)
      • 第4章 - 序列式容器 vector
      • 第4章 - 序列式容器 list
      • 第4章 - 序列式容器 deque
      • 第4章 - 序列式容器 stack和queue
      • 第4章 - 序列式容器 heap
      • 第4章 - 序列式容器 priority_queue
      • 第4章 - 序列式容器 slist
      • 第5章 - 关联式容器 RB-tree
      • 第5章 - 关联式容器 set和map
      • 第5章 - 关联式容器 hashtable
      • 第6章 - 算法
      • 第6章 - 算法之set
      • 第7章 - 仿函数
      • 第8章 - 配接器
  • 系统与架构

    • 深入理解计算机系统

      • 第1章 - 计算机系统漫游
      • 第2章 - 信息的表示和处理
      • 第3章 - 程序的机器级表示
      • 第5章 - 优化程序性能
      • 第6章 - 存储器层次结构
      • 第7章 - 链接
      • 第8章 - 异常控制流
      • 第9章 - 虚拟内存
      • 第10章 - 系统级I/O
      • 第11章 - 网络编程
      • 第12章 - 并发编程
    • 大型网站技术架构——核心原理与案例分析

      • 第1章 - 大型网站架构演化
      • 第2章 - 大型网站架构模式
      • 第3章 - 大型网站核心架构要素
      • 第4章 - 瞬时响应:网站的高性能架构
      • 第5章 - 万无一失:网站的高可用架构
      • 第6章 - 永无止境:网站的伸缩性架构
      • 第7章 - 随需应变:网站的可扩展架构
      • 第8章 - 固若金汤:网站的安全架构
    • 从零开始学架构

      • 架构基础
      • 架构设计原则
      • 高性能架构
      • 高可用架构
    • 程序员的自我修养————链接、装载与库

      • 第1章 - 简介
      • 第2章 - 静态链路
      • 第3章 - 目标文件里有什么
      • 第4章 - 静态链接
      • 第7章 - 动态链接
      • 第8章 - 共享库版本
      • 第10章 - 内存
      • 第11章 - 运行库
      • 第12章 - 系统调用与API
      • 第13章 - 运行库实现
  • 软件开发实践

    • 重构改善既有代码的设计

      • 第1章 - 重构,第一个示例
      • 第2章 - 重构的原则
      • 第3章 - 代码的坏味道
      • 第5章 - 重构列表
      • 第6章 - 重新组织函数
      • 第7章 - 在对象之间搬移特性
      • 第8章 - 重新组织数据
      • 第9章 - 简化条件表达式
      • 第10章 - 简化函数调用
      • 第11章 - 处理概括关系
      • 第12章 - 设计之大型重构
    • 代码大全2

      • 第1章 - 欢迎进入软件构建的世界
      • 第2章 - 用隐喻来更充分地理解软件开发
      • 第3章 - 三思而后行: 前期准备
      • 第4章 - 关键的构建决策
      • 第5章 - 软件构建中的设计
    • Linux多线程服务端编程——使用muduo C++ 网络库

      • Buffer类的设计
      • 设计与实现
      • 定时器与TimerQueue
      • Protobuf网络传输和Protobuf编解码器与消息分发器
      • EventLoop类剖析
      • EventLoopThread和EventLoopThreadPool剖析
      • TCP网络库和核心类
      • Connector剖析
      • TcpClient剖析
      • 学习总结
      • timing wheel
      • 消息广播服务
      • 线程安全的对象生命期管理
  • 数据处理与应用设计

    • 数据密集型应用系统设计

      • 第1章 - 可靠、可扩展与可维护的应用系统
      • 第2章 - 数据模型与查询语言
      • 第3章 - 数据存储与检索
      • 第4章 - 数据编码与演化
      • 第5章 - 数据复制
      • 第6章 - 数据分区
      • 第7章 - 事务

第2章:空间配置器

    空间配置器概述

    从STL的实现角度来看,空间配置器的位置尤为重要,整个STL的操作对象(所有的数值)都是存放在容器之内,而容器一定需要配置空间以存放资料。空间配置器就是为各个容器提供高效的管理空间。SGI STL的配置器与众不同,也与标准规范不同,其名称为alloc而非allocator,而且不接受任何参数。

    不能采用标准写法:

    vector<int,std::allocator<int>> iv;  // inv VC or CB
    

    应该写成:

    vector<int,std::alloc> iv;    // in GCC
    

    而且在使用容器中,一般很少指定配置器名称,这时候默认缺省的配置器为alloc,例如:

    template <class T,class Alloc = alloc>  //缺省使用alloc为配置器
    class vector {...};
    

    其实SGI也存在标准的空间配置器std::allocator,但是因为效率不高,也不建议使用 一般而言,我们习惯的C++内存配置和释放操作是这样的:

    class A {};
    A* pa = new A;
    //...执行其他操作
    delete pa;
    

    这过程中的new算式内含两阶段操作:

    1. 调用 ::operator new 配置内存;
    2. 调用 A::A() 构造对象内容。

    delete算式也内含两阶段操作:

    1. 调用 A::~A() 将对象析构;
    2. 调用 ::operator delete 释放内存。

    但是,为了最大化提升效率,SGI STL版本并没有简单的这样做,而是采取了一定的措施,实现了更加高效复杂的空间分配策略。在SGI STL中,将对象的构造切分开来,分成空间配置和对象构造两部分:

    • 内存配置操作:alloc::allocate()实现
    • 内存释放操作:alloc::deallocate()实现
    • 对象构造操作: ::construct()实现
    • 对象释放操作: ::destroy()实现

    STL对象构造与释放结构图如下所示:

    STL_allocator

    构造和析构基本工具

    下面是<stl_construct.h>的部分内容,介绍了construct和destroy函数。

    template <class T1, class T2>
    inline void construct(T1* p, const T2& value) {
      new (p) T1(value);  // placement new; 唤起T1::T1(value)
    }
    
    // 第一个版本,接受一个参数
    template <class Tp>
    inline void destroy(Tp* pointer) {
      pointer->~Tp();  // 调用dtor ~T()
    }
    
    // 第二个版本,接受两个迭代器,函数设法找出元素类别,进而利用 __type_traits<>使用最佳措施
    template <class ForwardIterator>
    inline void destroy(ForwardIterator first, ForwardIterator last) {
      __destroy(first, last, VALUE_TYPE(first));
    }
    

    针对 construct和destroy函数的泛化和特化版本示意图如下:

    construct和destory

    空间的配置与释放,std::alloc

    第二小节讲述了对象构造和对象析构的行为,现在来看看内存的配置和释放。在SGI版本的STL中,空间的配置和释放都由< stl_alloc.h > 负责。它的设计思想如下:

    • 向system heap要求空间
    • 考虑多线程(multi-thread)状态
    • 考虑内存不足的应变措施
    • 考虑过多“小型区块”可能导致的内存碎片问题(fragment)

    SGI采用malloc()和free()完成内存的配置与释放,考虑到小型区块所可能造成的内存碎片的问题,SGI设计了双层配置器,第一级配置器直接使用malloc()和free();第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,调用第一级配置器;当配置区块小于128bytes时,为了降低额外负担,便采用复杂的memory pool整理方式,而不再求助第一级配置。STL规定了 __USE_MALLOC 宏,如果它存在则直接调用第一级配置器,不然则直接调用第二级配置器。SGI STL未定义该宏,也就是说默认使用第二级配置器。

    第一和第二级配置器的关系如下:

    第一和第二级配置器

    第一和第二级配置器包装接口和运行方式

    从上述图可以看出,SGI STL提供了一层更高级的封装,定义了一个simple _ alloc类,无论是用哪一级都以模板参数alloc传给simple _ alloc,这样对外体现都是只是simple _ alloc:

    template<class _Tp, class _Alloc>
    class simple_alloc {
    
    public:
        static _Tp* allocate(size_t __n)
          { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }
        static _Tp* allocate(void)
          { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }
        static void deallocate(_Tp* __p, size_t __n)
          { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }
        static void deallocate(_Tp* __p)
          { _Alloc::deallocate(__p, sizeof (_Tp)); }
    };
    

    第一级配置器:直接调用malloc和free来配置释放内存,简单明了。

    //㆒般而言是 thread-safe,并且对于空间的运用比较高效(efficient)。
    //以下是第一级配置器。
    //注意,无「template型别参数」。至于「非型别参数」inst,完全没派上用场。
    template <int __inst>
    class __malloc_alloc_template {
    
    private:
    // 函数指针,处理内存不足情况
      static void* _S_oom_malloc(size_t);
      static void* _S_oom_realloc(void*, size_t);
    
    #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
      static void (* __malloc_alloc_oom_handler)();
    #endif
    
    public:
    
      static void* allocate(size_t __n)
      {
        // 第一级配置器直接使用malloc
        void* __result = malloc(__n);
        // 无法满足需求时,改用oom方法
        if (0 == __result) __result = _S_oom_malloc(__n);
        return __result;
      }
    
      static void deallocate(void* __p, size_t /* __n */)
      {
        free(__p); // 第一级配置器直接使用free
      }
    
      static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
      {
        // 第一级配置器直接使用realloc()
        void* __result = realloc(__p, __new_sz);
        // 无法满足需求时使用oom_realloc()
        if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
        return __result;
      }
    
      // 以下模拟c++的set_new_handler(),可以指定自己的oom handler
      static void (* __set_malloc_handler(void (*__f)()))()
      {
        void (* __old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = __f;
        return(__old);
      }
    
    };
    

    第二级配置器:根据情况来判定,如果配置区块大于128bytes,说明“足够大”,调用第一级配置器,而小于等于128bytes,则采用复杂内存池(memory pool)来管理。第二级空间配置器的过程,重点关注allocate和deallocate这两个函数的具体实现。

    static void* allocate(size_t n)
    {
        if (n > (size_t)__MAX_BYTES) // 字节数大于128,调用一级空间配置器
            return malloc_alloc::allocate(n);
        //不然到freelist去找
        Obj* volatile* myFreeList = FreeList + FreeListIndex(n); //定位下标
        Obj* ret = *myFreeList;
        if (ret == NULL)
        {
            void* r = refill(Round_UP(n));//没有可用free list 准备装填
        }
        *myFreeList = ret->freeListLink;
        return ret; 
    }
    

    可以看出allocate的处理逻辑为:

    • 如果用户需要的区块大于128,则直接调用第一级空间配置器
    • 如果用户需要的区块小于等于128,则到自由链表中去找
      • 如果自由链表有,则直接去取走
      • 不然则需要装填自由链表(refill)
    static void deallocate(void* p, size_t n)
    {
        if (n > (size_t)__MAX_BYTES) //区块大于128, 则直接由第一级空间配置器收回
            malloc_alloc::deallocate(p, n);
        Obj* volatile* myFreeList = FreeList + FreeListIndex(n);
        Obj* q = (Obj*)p;
        q->freeListLink = *myFreeList;
        *myFreeList = q;
    }
    

    释放操作deallocate和上面处理类似:

    • 如果区块大于128, 则直接由第一级空间配置器收回
    • 如果区块小于等于128, 则有自由链表收回

    我们在上面重点分析了整体思路,也就是二级配置器如何配置和申请内存,他们和一级配置器一样都提供allocate和deallocate的接口(其实还有个reallocate也是用于分配内存,类似于C语言中realloc函数),我们都提到了一点自由链表,那么自由链表是个什么?

    自由链表

    如上图所示,自由链表是一个指针数组,有点类似与hash桶,它的数组大小为16,每个数组元素代表所挂的区块大小,比如free _ list[0]代表下面挂的是8bytes的区块,free _ list[1]代表下面挂的是16bytes的区块…….依次类推,直到free _ list[15]代表下面挂的是128bytes的区块。同时,我们还有一个被称为内存池地方,以start _ free和 end _ free记录其大小,用于保存未被挂在自由链表的区块,它和自由链表构成了伙伴系统。

    我们之前讲了,如果用户申请小于等于128的区块,就到自由链表中取,但是如果自由链表对应的位置没了怎么办???这下子就轮到我们的内存池就发挥作用了!

    下面重点讲述一下,如果自由链表对应的位置没有所需的内存块,那么应该如何处理,即refill函数的实现。

    static void* allocate(size_t n)
    {
    //...
        if (ret == NULL)
        {
            void* r = refill(Round_UP(n));//没有可用free list 准备装填
        }
    //...
    }
    
    //freelist没有可用区块,将要填充,此时新的空间取自内存池
    static void* refill(size_t n)
    {
        size_t nobjs = 20;
        char* chunk = (char*)ChunkAlloc(n, nobjs); //默认获得20的新节点,但是也可能小于20,可能会改变nobjs
        if (nobjs == 1) //如果只有一块直接返回调用者,此时freelist无结点
            return chunk;
        //有多块,返回一块给调用者,其他挂在自由链表中
        Obj* ret = (Obj*)chunk;
        Obj* cur = (Obj*)(chunk + n);
        Obj* next = cur;
        Obj* volatile *myFreeList = FreeList + FreeListIndex(n);
        *myFreeList = cur;
        for (size_t i = 1; i < nobjs; ++i)
        {       
            next = (Obj*)((char*)cur + n);
            cur->freeListLink = next;
            cur = next;
        }
        cur->freeListLink = NULL;
        return ret;
    
    }
    

    这里面的重点函数为ChunkAlloc,它的逻辑相对复杂,代码如下:

    static char* ChunkAlloc(size_t size, size_t& nobjs)
    {
        size_t bytesLeft = endFree - startFree; //内存池剩余空间
        size_t totalBytes = size * nobjs;
        char* ret = NULL;
        if (bytesLeft >= totalBytes) // 内存池大小足够分配nobjs个对象大小
        {
            ret = startFree;
            startFree += totalBytes;
            return ret;
        }
        else if (bytesLeft >= size) // 内存池大小不够分配nobjs,但是至少分配一个
        {
            size_t nobjs = bytesLeft / size;
            totalBytes = size * nobjs;
            ret = startFree;
            startFree += totalBytes;
            return ret;
        }
        else // 内存池一个都分配不了
        {
            //让内存池剩余的那么点挂在freelist上
            if (bytesLeft > 0)
            {
                size_t index = FreeListIndex(bytesLeft);
                ((Obj*)startFree)->freeListLink = FreeList[index];
                FreeList[index] = (Obj*)startFree;
            }
    
            size_t bytesToGet = 2 * totalBytes + RoundUP(heapSize >> 4);
            startFree = (char*)malloc(bytesToGet);
            if (startFree == NULL)
            {
                //申请失败,此时试着在自由链表中找
                for (size_t i = size; i <= __MAX_BYTES; i += __ALIGN)
                {
                    size_t index = FreeListIndex(i);
                    Obj* volatile* myFreeList = FreeList + index;
                    Obj* p = *myFreeList;
                    if (FreeList[index] != NULL)
                    {
                        FreeList[index] = p->freeListLink;
                        startFree = (char*)p;
                        endFree = startFree + i;
                        return ChunkAlloc(size, nobjs);
                    }
                }
                endFree = NULL;
                //试着调用一级空间配置器
                startFree = (char*)MallocAlloc::Allocate(bytesToGet);
            }
    
            heapSize += bytesToGet;
            endFree = startFree + bytesToGet;
            return ChunkAlloc(size, nobjs);
        }
    
    }
    

    分析上述代码,我们知道当自由链表中没有对应的内存块,系统会执行以下策略:

    如果用户需要是一块n字节的区块,且n <= 128(调用第二级配置器),此时refill填充是这样的:(需要注意的是**:系统会自动将n字节扩展到8的倍数也就是Round_UP(n),再将Round_UP(n)传给refill)。用户需要n块,且自由链表中没有,因此系统会向内存池申请nobjs * n大小的内存块**,默认nobjs=20

    • 如果内存池大于 nobjs * n,那么直接从内存池中取出
    • 如果内存池小于nobjs * n,但是比一块大小n要大,那么此时将内存最大可分配的块数给自由链表,并且更新nobjs为最大分配块数x (x < nobjs)
    • 如果内存池连一个区块的大小n都无法提供,那么首先先将内存池残余的零头给挂在自由链表上,然后向系统heap申请空间,申请成功则返回,申请失败则到自己的自由链表中看看还有没有可用区块返回,如果连自由链表都没了最后会调用一级配置器.

    这就是ChunkAlloc所执行的操作,在执行完ChunkAlloc函数后会获得内存(失败就抛出异常),此时也就是这段代码:

    if (nobjs == 1) //如果只有一块直接返回调用者,此时freelist无结点
            return chunk;
        //有多块,返回一块给调用者,其他挂在自由链表中
        Obj* ret = (Obj*)chunk;
        Obj* cur = (Obj*)(chunk + n);
        Obj* next = cur;
        Obj* volatile *myFreeList = FreeList + FreeListIndex(n);
        *myFreeList = cur;
        for (size_t i = 1; i < nobjs; ++i)
        {       
            next = (Obj*)((char*)cur + n);
            cur->freeListLink = next;
            cur = next;
        }
        cur->freeListLink = NULL;
    
    

    如果只有一块返回给调用者,有多块,返回给调用者一块,剩下的挂在对应的位置。

    # 内存基本处理工具

    STL定义有五个全局函数,这五个函数分别是construct(),destory(),uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(),都作用与未初始化的内存空间上。construct()和destory()上述章节已经讲解过,不再赘述。现在让我们了解下另外三个函数:uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(),分别对应于高层次函数copy()、fill()、fill_n()——这些都是STL的算法。它们都能够将内存的配置和对象的构造行为分离开来。

    • uninitialized_copy() 首先会在内存中分配一片内存(未构造),然后接受输入端的开始迭代器和输入端结束迭代器,对此输入的对象进行copy,然后放进分配的未构造的内存中。
    template inline ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result)
    {
    	return _uninitialized_copy(first, last, result, value_type(result));
    	//以上利用value_type()取出first的value_type
    }
    
    template inline ForwardIterator _uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result, T*)
    {
    	typedef typename _type_traits::is_POD_type is_POD;
    	return _uninitialized_copy_aux(first,last,result,is_POD());
    }
    
    //如果是POD型别,执行流程就会转到以下函数
    template inline ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, _true_type)
    {
    	return copy(first,last,result); //调用STL算法copy,效率高
    }
    
    //如果不是POD型别,执行流程就会转到以下函数
    template
    ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, _false_type)
    {
    	ForwardIterator cur = result;
    	for (; first != last; ++first, ++cur)
    		construct(&*cur,*first);  //必须一个一个元素构造,无法批量处理
    	return cur;
    }
    

    这个函数的逻辑是,首先萃取出迭代器result的value type,然后判断该型别是否为POD型别。POD意指Plain Old Data,也就是标量型别(scalar types)或传统的C struct型别。POD型别必然拥有 trivial ctor/dtor/copy/assignment函数,因此我们可以对POD型别采用最有效率的复制手法(采用STL的copy函数),而对non-POD型别采用最保险安全的做法(采用construct函数,一个个构造)。

    uninitialized_fill()接受输入端的开始迭代器和输入端的结束迭代器(此范围内的迭代器指向未构造的内存),然后接受一个对象,把对象copy到此迭代器指向的未初始化的内存,也就是它会将指定范围内的未构造的内存指定相同的对象。

    template inline void uninitialized_fill(ForwardIterator first,ForwardIterator last,const T& x)
    {
        _uninitialized_fill(first, last, x, value_type(first));
    	//以上利用 value_type()取出first的 value_type
    }
    
    template inline void _uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x, T1*)
    {
    	typedef typename _type_traits::is_POD_type is_POD;
    	_uninitialized_fill_aux(first, last, x, is_POD());
    }
    
    //如果是POD型别,执行流程就会转到以下函数
    template inline void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, _true_type)
    {
    	fill(first, last, x);  //调用STL算法fill
    }
    
    //如果不是POD型别,执行流程就会转到以下函数
    template inline void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, _false_type)
    {
    	ForwardIterator cur = first;
    	for (; cur!=last;++cur)
    		construct(&*cur, x); //必须一个一个元素构造,无法批量处理
    }
    

    这个函数的逻辑是,与上述uninitialized_fill处理类似。对POD型别采用最有效率的复制手法(采用STL的fill函数),而对non-POD型别采用最保险安全的做法(采用construct函数,一个个构造)。

    uniniyialized_fill_n()接受输入端的开始处的迭代器(此分配的内存是未构造的)和初始化空间的大小,最重要的是它会给指定范围内的未构造的内存指定相同的对象。

    template inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x)
    {
    	return uninitialized_fill_n(first,n,x,value_type(first));
    	//以上利用 value_type()取出first的 value_type
    }
    
    template inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x, T1*)
    {
    	typedef typename _type_traits::is_POD_type is_POD;
    	return uninitialized_fill_n_aux(first, n, x, is_POD());
    }
    
    //如果是POD型别,执行流程就会转到以下函数
    template inline ForwardIterator uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, _true_type)
    {
    	return fill_n(first,n,x); //调用STL算法fill_n
    }
    
    //如果不是POD型别,执行流程就会转到以下函数
    template ForwardIterator uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, _false_type)
    {
    	ForwardIterator cur = first;
    	for (; n > 0; --n, ++cur)
    		construct(&*cur,x); //必须一个一个元素构造,无法批量处理
    	return cur;
    }
    

    这个函数的逻辑是,与上述uninitialized_fill处理类似。对POD型别采用最有效率的复制手法(采用STL的fill_n函数),而对non-POD型别采用最保险安全的做法(采用construct函数,一个个构造)。

    总结

    uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n()都会根据迭代器的value type,判断其是否为POD型别:

    1. 为POD型别的对象提供高阶函数以提高处理效率;
    2. 为non-POD型别提供construct函数以保证构造过程。

    ​

    Last Updated:
    Contributors: klc407073648
    Prev
    第1章 - STL概论和版本简介
    Next
    第3章 - 迭代器(iterators)概念与traits编程技法(一)