Modern C++
Grammar
std::ranges
std::ranges 库的核心思想是让算法直接操作容器(而非迭代器对),并支持管道式(pipeline)链式调用。
对比以前的迭代器使用方式:
| |
以下是一些std::ranges的常用方式:
转换:
- std::transform
过滤:
- std::filter
排序:
- std::sort
查找:
std::find
遍历:
for_each
1 2 3// 将数组内所有元素置0 std::vector<int> matrix{0, 1, 0, 2, 3}; std::ranges::for_each(matrix, [](auto& value) { value = 0; });
判断是否存在满足条件的元素:
- contains
1 2 3// 检查是否包含0元素 std::vector<int> matrix{0, 1, 0, 2, 3}; bool hasZero = std::ranges::contains(matrix, 0); - any_of
1 2 3 4 5// 检查是否包含0元素 std::vector<int> matrix{0, 1, 0, 2, 3}; bool hasZero = std::ranges::any_of(matrix, [](auto& value) -> bool { return value == 0 });
其他:
take
std::filesystem
容器小tips
大小根堆
std::priority_queue为标准库自带的大根堆,默认为:std::priority_queue<T, std::vector<T>, std::less<>>,自定义排序版的堆为:
| |
std::priority_queue本身为对<algorithm>中make_heap、push_heap、pop_heap、is_heap等方法的封装,所以第二个模板参数需要提供支持随机访问的容器:
| |
基于vector手动实现堆排序:
| |
Container/Tool Class
std::string
| |
一些小细节
比较运算符
关于比较运算符的重载,C++20提供了更高效的 <=> 运算符重载,可以更高效的重载比较运算符,所以上面的代码可以修改为:
| |
对于比较结果,C++20提供了三种不同强度的类型:
| |
友元
C++ 中有三种友元形式:友元函数、友元类、友元成员函数:
友元函数
类内定义的友元函数:
| |
这种定义方式会使得函数在全局命名空间不可见,只有当参数中包含Object类时,才会在Object的命名空间中查找该函数。对于Object(2) == 2 和 2 == Object(2),如果没有friend,后者会编译失败(在C++20之前)。
C++20之后引入了新的
==运算符规则,允许编译器翻转运算符两边的内容)。
普通友元函数:
| |
适用于同时需要访问两个类的私有成员的场景。
友元类
| |
友元成员函数
| |
std::vector
堆、栈
std::map
线程池
无锁队列
Algorithm Template
排序
快速排序
| |
时间复杂度:
对于理想待排序数组,即每次都能正好平均分原始数组时,树的递归深度(即“循环次数”)为:将数组长度N除以2直到长度为1或0的次数,即:N/2^x = 1,x = log2N。对于每一层树的处理,所有节点长度总是N,所以最终复杂度为:O(NlogN)。
对于最坏情况,即每次拆分数组都只能将原始数组分成
0和n - 1的两个子数组,此时循环次数会退化回N,时间复杂度也来到最差情况:O(N^2)对于std::sort,内部在选基准点时会选取前、中、后三个基准点,并取居中的数作为基准以避免极端情况,当递归次数过多时也会退化为“堆排序”。
快速排序的优势在于:每次处理子数组都是顺序处理,CPU友好,并且空间复杂度为递归深度O(logN),且没有资源拷贝开销。
Partition
上面的快排的Partition实现采用了快慢指针法,代码实现精简,但是可以注意到,对于近乎有序的数组,比如:
| |
在初次排序时,需要swap七次才能将数字1放到他该放到的位置,这对于讲究速率的排序算法来说很致命,所以,我们可以通过双指针法来避免上述问题:
| |
这种算法可以尽量避免不必要的交换,一定程度上提高排序的速度,毕竟Partition的目的只是分区。
如果要支持迭代器类型,可以将参数做如下修改:
| |
这里的RandomIt可以换成C++20的std::random_access_iterator,以限制迭代器为随机访问迭代器,否则当传入的迭代器类型为双向迭代器时,std::distance复杂度会退化成O(n),通过while (++it)来前进计算距离。
同时对于一长串的迭代器类型萃取,c++14提供了透明比较器 std::less<>,可以在进行比较时根据传入的参数来生成相应的实例化代码,区别在于前者仅支持相同类型的 ‘<’ 比较。
归并排序
| |
并查集
Trie 树
LRU
| |
二叉平衡树
Qt
OpenGL
杂项记录
glBufferData
glBufferData是用来向GPU申请缓存的函数:
| |
其中target不同,用处不同:
| target | 含义 |
|---|---|
GL_ARRAY_BUFFER | 顶点数据 |
GL_ELEMENT_ARRAY_BUFFER | 索引数据 |
GL_UNIFORM_BUFFER | uniform 数据 |
GL_SHADER_STORAGE_BUFFER | 计算数据 |
GL_PIXEL_UNPACK_BUFFER | 纹理上传 |
针对data参数,传入非空时会将数据从CPU copy到 GPU,传NULL时会等待glBufferSubData或glMapBuffer写数据(与usage参数无关)。
最后一个usage参数不会改变 API 行为,其作用是调整OpenGL驱动对GPU缓存的优化策略:
| usage | 含义 |
|---|---|
GL_STATIC_DRAW | 数据几乎不变 |
GL_DYNAMIC_DRAW | 会偶尔修改 |
GL_STREAM_DRAW | 每帧更新 |
所以,对于GL_STATIC_DRAW,也可以在申请缓存时将data字段传NULL,但是会导致每次调用glBufferSubData时消耗增加。
glBufferSubData 与 glMapBuffer
glBufferSubData当
glBufferData的数据部分传空时需要使用glBufferSubData进一步动态更新数据:1 2glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferSubData(GL_ARRAY_BUFFER, 0, size, data);这里会存在一个
pipeline stall(或CPU stall),即GPU流水线上堆积了任务,正在使用该缓存,但是此时CPU又要进行写入,这就会造成CPU的等待,可以通过buffer orphaning(缓存孤立)来缓解这种停顿现象:1 2 3glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, size, data);glMapBuffer1 2 3 4glBindBuffer(GL_ARRAY_BUFFER, vbo); void* ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); // use ptr to copy data glUnmapBuffer(GL_ARRAY_BUFFER);可以直接将数据从文件拷贝到共享buffer,减少拷贝?(是否可以类比于Linux下的mmap?)
glMapBuffer 与 glBufferSubData,返回 ·SteveStreeting.com 有博客提到在大数据量的情况下glMapBuffer性能高于glBufferSubData,并且存在一个阈值。
