STL分析

news/2024/9/26 0:17:34/文章来源:https://www.cnblogs.com/929code/p/18432588

一. 配置器

配置器(Allocator) 负责为容器(如 vector、list 等)分配和释放内存,以及对象的构造和析构。
这也是为什么vector等容器不用显式的回收内存,采用的是通过类对象来创建回收资源的RALL思想

1. 类模板的使用

类模板是一种可以在声明时不指定具体类型的方式,使得类可以在多个不同类型的场景下复用。

在代码中,allocator使用了模板参数 T 来表示类型。例如:

template <class T>
class allocator
{// 类中的代码
};

通过template <class T>allocator可以处理不同的数据类型,具体类型在实例化时才决定,比如 allocator<int> 会针对 int 类型分配内存。

此外,在类外定义成员函数时,仍需使用 template <class T> 再次声明,这是模板类成员函数的标准写法:

template <class T>
T* allocator<T>::allocate()
{return static_cast<T*>(::operator new(sizeof(T)));  // 为 T 类型分配内存
}

这样,在实例化时,可以为 T 类型的对象分配内存,类型 T 是灵活变化的。

2. 命名空间的使用

命名空间用于防止不同代码块之间的命名冲突。在你的代码中,mystl命名空间包含了allocator类,避免了与其他库中的allocator命名冲突:

namespace mystl
{template <class T>class allocator{// 内存分配器的定义};
}

mystl命名空间表明该分配器是属于用户自定义的,并且可以在不同的代码库中独立存在。使用时可以通过mystl::allocator来访问该类,避免与其他库中的 allocator 类混淆。

3. 可变参数模板与完美转发

可变参数模板是C++11引入的一项功能,允许模板接受不定数量的模板参数和函数参数。与传统的C语言可变参数不同,可变参数模板支持类型安全。

在代码中,allocator类使用了可变参数模板来处理构造函数的不同调用方式:

template <class... Args>
static void construct(T* ptr, Args&& ...args)
{mystl::construct(ptr, mystl::forward<Args>(args)...);  // 完美转发
}

template <class... Args> 定义了可以接受任意类型和数量参数的模板函数。Args&& 表示参数包,这里使用了 万能引用 来接收所有参数,包括左值和右值。

mystl::forward<Args>(args)... 通过 完美转发 保留了参数的原始类型性质(左值、右值),并将它们传递给下游的构造函数。这种方式确保了高效的参数传递,避免不必要的拷贝。

4. 四种类型的重载

allocator中的construct函数展示了四种不同的重载方式,每种重载都适用于不同的场景:

// 1. 默认构造
static void construct(T* ptr)
{mystl::construct(ptr);  // 调用默认构造函数
}// 2. 拷贝构造
static void construct(T* ptr, const T& value)
{mystl::construct(ptr, value);  // 拷贝构造
}// 3. 移动构造
static void construct(T* ptr, T&& value)
{mystl::construct(ptr, mystl::move(value));  // 移动构造
}// 4. 可变参数构造
template <class... Args>
static void construct(T* ptr, Args&& ...args)
{mystl::construct(ptr, mystl::forward<Args>(args)...);  // 完美转发构造
}

5. 内存分配和释放

allocator使用 ::operator new::operator delete 来分配和释放内存。值得注意的是,前面的 :: 是作用域解析运算符,用来明确调用的是全局的 operator newoperator delete

// 分配单个对象的内存
static T* allocate()
{return static_cast<T*>(::operator new(sizeof(T)));  // 使用全局 new 分配
}// 分配多个对象的内存
static T* allocate(size_type n)
{if (n == 0) return nullptr;return static_cast<T*>(::operator new(n * sizeof(T)));  // 分配 n 个对象的内存
}// 释放单个对象的内存
static void deallocate(T* ptr)
{if (ptr == nullptr) return;::operator delete(ptr);  // 使用全局 delete 释放内存
}

这里的 ::operator new::operator delete 用于手动分配和释放原始内存(没有调用构造函数和析构函数),这与C++标准库 newdelete 不同,它们同时会调用构造函数和析构函数。

6. 构造与内存分配的分离:灵活与高效

在C++中,内存分配和对象构造的分离有其明确的优势,特别是在复杂的内存管理场景下:

  • 延迟构造:通过先分配内存,再在需要时构造对象,提供了更灵活的内存和对象管理方式。例如,你可以分配一块内存但等到特定时刻才开始构造对象。
  • 批量管理:内存分配的开销通常比构造和销毁对象大得多,将分配与构造分离允许在分配一次内存后,在该内存中多次构造和销毁对象,优化了性能。
  • 优化资源回收:你可以销毁对象后保留分配好的内存,等待下次构造操作使用,这在内存池或容器中非常常见。它减少了频繁分配和释放内存的开销,提升了资源管理效率。

7. 析构与内存释放的分离

与构造和内存分配类似,析构对象和释放内存的分离也有重要意义:

  • 灵活销毁:有时你可能只想销毁对象,而暂时不释放内存。例如,预先分配好一块大内存块,并在其上反复构造和销毁对象,这在内存池中非常常见。
  • 批量释放:在某些场景中,你可能不需要一个一个地销毁对象(特别是当对象类型为 平凡析构 时),而是直接释放整块内存。通过将 destroydeallocate 分开,可以选择是否显式调用析构函数。
  • 异常安全性:析构与释放的分离允许在异常发生时,能够选择性地销毁对象,而确保内存能够被安全地回收,避免内存泄漏。

8. 定位 new(Placement new):内存控制的关键工具

construct 函数中,你看到了 placement new 的使用:

::new ((void*)ptr) Ty(mystl::forward<Args>(args)...);
  • 定位 newplacement new 是一种特殊的 new 运算符,它不分配新的内存,而是在已有的内存位置(通过指针 ptr 提供)上构造对象。
    这个技术是分离内存分配与对象构造的基础,允许开发者在预先分配的内存中手动调用构造函数。

使用 placement new 的原因是它赋予了开发者精确的内存控制能力,可以在同一块内存中反复构造和销毁对象,而不涉及频繁的内存分配和释放操作。它广泛用于内存池、STL容器和自定义分配器中。

9. 平凡与非平凡析构对象的差异化处理

destroy 函数中,类型特征被用来区分 平凡析构非平凡析构 对象:

template <class Ty>
void destroy(Ty* pointer)
{destroy_one(pointer, std::is_trivially_destructible<Ty>{});
}
  • 平凡析构trivially destructible)对象是指那些不需要显式调用析构函数的对象,例如基本类型(intfloat)或者不含有资源管理的类。这类对象的内存可以直接释放,而无需调用析构函数。
  • 非平凡析构对象则需要显式调用析构函数来释放资源(如指针、文件句柄等)。

通过区分这两类对象,代码在处理平凡类型时可以跳过析构步骤,提升效率。这种优化广泛应用于标准库的实现中,比如 std::vector 在处理基础类型时,就会避免不必要的析构调用。

  • std::is_trivially_destructible<T> 利用 模板特化 来判断类型 T 是否是平凡析构类型,返回 std::true_typestd::false_type,通过类型推导和模板实例化在 编译期 选择适当的函数重载,这体现了 静态多态 的思想。

  • 泛型编程允许代码对任意类型进行操作,提升了代码复用性;模板特化则提供了一种机制,根据具体类型在编译期生成特定实现;类型特征(如 std::is_trivially_destructible)通过类型萃取技术,在编译期获取类型的特性,从而实现高效、类型安全的程序设计。

10. 批量销毁中的迭代器支持

在批量销毁时,代码提供了对迭代器的支持,允许你销毁一段范围内的对象:

template <class ForwardIter>
void destroy(ForwardIter first, ForwardIter last)
{destroy_cat(first, last, std::is_trivially_destructible<typename iterator_traits<ForwardIter>::value_type>{});
}
  • 迭代器:通过接受两个迭代器 firstlast,可以销毁一段内存区域内的所有对象。
  • 优化:同样地,使用 std::is_trivially_destructible 来判断迭代器指向的对象是否需要销毁,可以进一步优化性能。

这种设计灵活性使得 destroy 函数能够轻松处理大多数标准容器(如 std::vectorstd::list)中的对象销毁操作,同时保证高效的内存管理。

二. 迭代器

迭代器的总结

在 C++ 中,迭代器是一个非常重要的概念,它们是容器与算法之间的桥梁,用来遍历容器中的元素。迭代器的设计使得同一套算法可以适用于不同类型的容器。迭代器的基本功能包括:

  • 遍历容器中的元素:通过迭代器可以逐个访问容器中的元素。
  • 支持前进、后退、随机访问等操作:不同类型的迭代器支持不同的操作,如输入迭代器支持读取,双向迭代器支持前进和后退,随机访问迭代器则可以直接通过索引访问元素。
  • 兼容性:C++ 标准库中设计的各种算法(如 std::sortstd::find)都是基于迭代器实现的,支持多种容器(如 vectorlistmap)的遍历。

迭代器可以分为五种类型:

  1. 输入迭代器(Input Iterator):只能读取元素,常见于输入流。
  2. 输出迭代器(Output Iterator):只能写入元素,常见于输出流。
  3. 前向迭代器(Forward Iterator):可以读取、写入,并且只能前向遍历。
  4. 双向迭代器(Bidirectional Iterator):可以前向和后退遍历。
  5. 随机访问迭代器(Random Access Iterator):支持常数时间内的随机访问,类似于数组的指针。

实现迭代器的理解

实现迭代器的核心思想是通过类型萃取和模板技术,使得不同容器的迭代器能够提供统一的接口,便于在泛型算法中进行操作。迭代器实现的核心功能包括:

  • 遍历容器:通过 ++-- 操作符前进和后退,或者通过 +=-= 在容器中移动。
  • 访问元素:通过 *-> 操作符访问迭代器当前指向的元素。
  • 计算迭代器之间的距离:不同类型的迭代器支持不同的距离计算方式。
  • 迭代器分类:通过 iterator_category,使算法能够区分不同的迭代器类型,并根据迭代器的能力选择最优的操作方式。

迭代器的基本接口:

  • iterator_traits:用于萃取迭代器的类型信息,如迭代器的类型、指针类型、引用类型、差值类型等。
  • advance 函数:让迭代器前进或后退特定的步数。
  • distance 函数:计算两个迭代器之间的距离。
  • reverse_iterator:实现反向迭代器,使得前进操作变为后退,后退变为前进,通常用于从后向前遍历容器。

串联用到的知识与功能实现

  1. 模板编程与泛型设计
    迭代器的实现大量依赖模板技术。通过模板编程,我们可以实现一个支持多种类型的迭代器,而无需为每种类型单独定义迭代器。模板允许迭代器对容器中的任意类型进行操作,并且在编译期确定具体的类型和操作方式。

  2. 类型萃取(Type Traits)
    iterator_traits 是类型萃取的核心,它用于从迭代器中提取类型信息,如 value_typepointerreference 等。这样可以让算法和迭代器分离,算法只需要知道如何通过类型萃取器获取迭代器的信息,而不需要关心迭代器的具体实现。

    例如

    typedef typename iterator_traits<Iterator>::iterator_category iterator_category;
    

    这行代码提取了 Iterator 迭代器的类型标签,让算法可以根据 iterator_category 选择合适的操作。

  3. 模板特化与静态多态
    迭代器的设计中使用了大量的模板特化,以实现针对不同迭代器类型的优化。例如,针对 random_access_iterator_tag,我们可以直接通过算术运算符计算迭代器的距离,而对其他类型的迭代器则需要逐个遍历计算。

    例如,对于 random_access_iterator_tag

    template <class RandomIter>
    typename iterator_traits<RandomIter>::difference_type
    distance_dispatch(RandomIter first, RandomIter last, random_access_iterator_tag) {return last - first;
    }
    
  4. 静态多态与编译期优化
    C++ 的模板和类型萃取机制使得迭代器的多态行为可以在 编译期 确定,而不是像传统的虚函数那样在运行时进行多态操作。这大大提升了性能,因为所有的类型判断和函数选择都在编译期间完成。

  5. 反向迭代器
    reverse_iterator 是 STL 中非常常见的迭代器类型,它通过包装正向迭代器,将前进变为后退,后退变为前进,从而实现反向遍历的功能。

    例如

    template <class Iterator>
    class reverse_iterator {
    private:Iterator current;
    public:reverse_iterator& operator++() {--current;return *this;}reverse_iterator& operator--() {++current;return *this;}
    };
    

    reverse_iterator 通过重载 ++-- 操作符改变正向迭代器的遍历方向。

结论

实现迭代器的复杂性在于如何提供一个泛型、灵活且高效的遍历接口。通过模板编程、类型萃取和模板特化,C++ 标准库成功实现了一个强大且通用的迭代器机制。每个迭代器可以根据不同容器的需求提供不同的功能,同时通过 iterator_traits 进行类型萃取,保证算法和迭代器的解耦,并且利用静态多态和编译期优化提升性能。

三. 算法

四. 容器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/803768.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PO、DTO、VO、BO 及其使用场景

基于 说清楚 PO、DTO、VO、BO 与使用场景简介PO(Persistent Object)/DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。 DTO(Data Transfer Object):数据传输对象。Service 或 Manager 向外传输的对象。 BO(Business Object):业务对象…

Java中到底有哪些锁

乐观锁和悲观锁 不是具体的锁,是指看待并发同步的角度 悲观锁:对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。 乐观…

Pyqt5 修改表格排序箭头

实现效果:代码from chatgptimport sys from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget from PyQt5.QtCore import Qtclass TableDemo(QWidget):def __init__(self):super().__init__()# 创建表格self.table_widget = QTabl…

day7[XTuner 微调个人小助手认知任务]

微调前用 internlm2-chat-1_8b 模型,通过 QLoRA 的方式来微调一个自己的小助手认知作为案例来进行演示

【算法】笔试题记录

哇今天做了道特别有意思的题。 编程就给了两道,第一题特别简单,a、b两个数,每次选其中一个数*2,这样操作两次,问最后得到的两数之和的期望值是多少。 简单吧?因为每次选择都有两种可能性,操作两次后就会有四种可能的结果(22)。其中有两个结果是重复的(2a, 2b),剩下两个…

使用AI进行需求分析的案例研究

生成式 AI 的潜在应用场景似乎无穷无尽。虽然这令人兴奋,但也可能让人不知所措。因此,团队在使用这项技术时需要有明确的目标:关键是要明确生成式 AI 在团队工作中能产生哪些实质性影响。 在软件工程中,一个引人注目的应用场景是需求分析。这是一个常常被忽视但充满挑战的环…

02 第三组(4个)进制转换

进制转换:二进制,十六进制、八进制、十进制 bin 二进制 oct 8进制 hex 十六进制 int 10进制二进制 和十进制#10进制转二进制 v1 = bin(48) print(v1)#二进制转10进制 v1 = 0b1010101 v2 = int(v1, base=2)八进制 和十进制#10进制转八进制 v1 = oct(48) print(v1)#八进制转1…

实验1_C语言输入输出和简单程序应用编程

任务一 1-1#include<stdio.h> int main() { printf(" O "); printf("<H>"); printf("I I"); printf(" O "); printf("<H>"); printf("I I"); return 0; }1-2#include<stdio.h> int main(…

2023-9-25

vscode快捷键实操练习

操作流程违规作业监测系统

操作流程违规作业监测系统基于计算机视觉深度学习技术,操作流程违规作业监测系统对石油煤矿化工等高危场景下作业人员未按照操作流程进行正常操作行为进行实时分析识别检测,如操作流程违规作业监测系统发现现场人员违规作业操作行为,不需人为干预,立即自动抓拍存档预警并同…

01 本地代码推送到码云

访问网站根据提示进行注册即可 https://gitee.com/新建仓库 注册后,进行登录,在右上角查看创建的代码仓库如果要分享别人,进行上传代码,将:https://gitee.com/jhchena/test.git 分享给别人即可 欢乐马 / test 中的test 表示在码云上面,创建存放代码的文件夹本地进行配置码云 先…