• 侯捷 C++ STL标准库和泛型编程 —— 4 分配器 + 5 迭代器


    4 分配器

    4.1 测试

    分配器都是与容器共同使用的,一般分配器参数用默认值即可

    list<string, allocator<string>> c1;
    
    • 1

    不建议直接用分配器分配空间,因为其需要在释放内存时也要指明大小

    int* p; 	
    p = allocator<int>().allocate(512, (int*)0); // 临时变量调用函数
    allocator<int>().deallocate(p,512); // 释放时需要指明之前申请的大小
    
    • 1
    • 2
    • 3

    4.2 源码解析

    VC6下:allocator 中有 allocatedeallocate 其分别用函数 ::operator new::operator delete 来调用 c 中的 mallocfree

    pointer allocate(size_type _N, const void*){...} // 后面一个参数只是用来指明类型的
    void deallocate(void _FARQ *_P, size_type){...}
    
    • 1
    • 2

    这里经过包装还是调用的 malloc 和 free,其执行效率变慢;且如果申请的空间比较小,会有较大比例的额外开销(cookie,调试模式所需空间等等)

    GCC2.9 下:其容器都是调用的名叫 alloc 的分配器

    在这里插入图片描述

    其从0到15有一共16个链表,分别代表8字节到16*8字节,例如 #0 的位置用 malloc 要一大块内存,然后做切割,切成一块一块的8字节空间不带cookie,用单向链表穿起来;当要申请6字节的大小的空间时,其就会到 #0 中占用一块 —— 节省空间

    在 GCC4.9 中各个容器又用回了 allocator,而上面的 alloc 变成了__poll_alloc

    5 迭代器

    5.1 迭代器的设计准则

    Iterator 必须提供5种 associated type(说明自己的特性的)来供算法来识别,以便算法正确地使用 Iterator

    template <class T, class Ref, class Ptr>
    struct __list_iterator
    {
        ...
    	typedef bidirectional_iterator_tag iterator_category; // (1)迭代器类别:双向迭代器	
    	typedef T value_type; // (2)迭代器所指对象的类型
    	typedef Ptr pointer; // (3)迭代器所指对象的指针类型
    	typedef Ref reference; // (4)迭代器所指对象的引用类型
    	typedef ptrdiff_t difference_type; // (5)两个迭代器之间的距离类型
        // iter1-iter2 时,要保证数据类型以存储任何两个迭代器对象间的距离
        ...
    
    }
    // 迭代器回答
    
    // | Λ
    // | |
    // | | 
    // V |
    
    // 算法直接提问
    template <typename I>
    inline void algorithm(I first, I last)
    {
        ...
        I::iterator_category
        I::pointer
        I::reference
        I::value_type
        I::difference_type
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    但当 Iterator 并不是 class 时,例如指针本身,就不能 typedef 了 —— 这时就要设计一个 Iterator Traits

    Traits:用于定义类型特征的信息,从而在编译时根据类型的不同进行不同的操作或处理 —— 类似一个萃取机(针对不同类型做不同操作:偏特化)

    image-20230827102754004
    // I是class iterator进
    template <class I>
    struct Iterator_traits
    {
    	typedef typename I::iterator_category iterator_category;
    	typedef typename I::value_type value_type;
    	typedef typename I::difference_type difference_type;
    	typedef typename I::pointer pointer;
    	typedef typename I::reference reference;
        // typename用于告诉编译器,接下来的标识符是一个类型名,而不是一个变量名或其他名称
        // I::iterator_category 是一个类型名
        // iterator_category是这个迭代器类型内部的一个嵌套类型(typedef ...)
    };
    
    // I是指向T的指针进
    template <class T>
    struct Iterator_traits<T*>
    {
    	typedef random_access_iterator_tag iterator_category;
    	typedef T value_type;
    	typedef ptrdiff_t difference_type;
    	typedef T* pointer;
    	typedef T& reference;
    };
    
    // I是指向T的常量指针进
    template <class T>
    struct Iterator_traits<const T*>
    {
    	typedef random_access_iterator_tag iterator_category;
    	typedef T value_type; // 注意是T而不是const T
        // 按理说是const T,但声明一个不能被赋值的变量无用
        // 所以value_type不应加上const
    	typedef ptrdiff_t difference_type;
    	typedef const T* pointer;
    	typedef const T& reference;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    除了 Iterator Traits,还有很多其他 Traits

    5.2 迭代器的分类

    迭代器的分类对算法的效率有很大的影响

    1. 输入迭代器 input_iterator_tag:istream迭代器
    2. 输出迭代器 output_iterator_tag:ostream迭代器
    3. 单向迭代器 forward_iterator_tag:forward_list,hash类容器
    4. 双向迭代器 bidirectional_iterator_tag: list、红黑树容器
    5. 随机存取迭代器 random_access_iterator_tag:array、vector、deque
    image-20230831085955167

    用有继承关系的class实现:

    1. 方便迭代器类型作为参数进行传递,如果是整数的是不方便的
    2. 有些算法的实现没有实现所有类型的迭代器类别,就要用继承关系去找父迭代器类别
    struct input_iterator_tag {};
    struct output_iterator_tag {};
    struct forward_iterator_tag : public input_iterator_tag {};
    struct bidirectional_iterator_tag : public forward_iterator_tag {};
    struct random_access_iterator_tag : public bidirectional_iterator_tag {};
    
    • 1
    • 2
    • 3
    • 4
    • 5

    算法 distance 将会按照迭代器的类别进行不同的操作以提升效率

    • 如果迭代器可以跳,直接 last - first 即可
    • 如果迭代器不能跳,就只能一步一步走来计数

    两者的效率差别很大

    image-20230902091354849

    但如果迭代器类别是 farward_iterator_tag 或者 bidirectional_iterator_tag,该算法没有针对这种类型迭代器实现,就可以用继承关系来使用父类的实现(继承关系——“is a” 子类是一种父类,当然可以用父类的实现)

    算法 copy 将经过很多判断筛选来找到最高效率的实现

    其中用到了 Iterator TraitsType Traits 来进行筛选

    has trivial op=() 是指的有不重要的拷贝赋值函数(例如复数用的自带的拷贝赋值函数)

    image-20230902093014515

    注意:由于 output_iterator_tag(例如 ostream_iterator)是 write-only,无法用 * 来读取内容,所以在设计时就需要再写个专属版本

    在源码中,算法都是模板函数,接受所有的 iterator,但一些算法只能用特定的 iterator,所以其会在模板参数的名称上进行暗示:

  • 相关阅读:
    四、Qt自定义UI界面(细节的使用)
    26.XML
    Codeforces Global Round 23 E CF1746E Joking (Hard Version)
    【课外阅读】cpp并发编程实战
    Python绘图系统24:添加辅助坐标轴
    光模块在5G网络中的使用
    Centos7安装Redis
    医疗大模型:数据+知识双轮驱动实现医学推理、医患问答、病历自动生成、临床决策,为未来医疗服务提供全新可能性
    接口数据源变更,用 DeepDiff 测
    jmeter性能测试常见的一些问题
  • 原文地址:https://blog.csdn.net/WJwwwwwww/article/details/133455151