STL 重载比较操作符 随机比较问题
问题描述
C++ struct 重载比较操作符 可以实现按照重载函数的规则排序;
如果 重载比较操作符 时,不是按照确定的大小顺序,而是一种随机的大小问题,会出现访问越界的问题?
问题复现
在写代码的过程中,出现了上述的问题,简化后的代码如下,可以在多次运行中出现core
#include<bits/stdc++.h>
using namespace std;
struct Item {
string info_;
string info2_;
string info3_;
bool bool1_;
int64_t id_;
bool bool2_;
Item(const string& info,
const string& info2,
const string& info3,
const bool boo1,
const int64_t id)
:info_(info),
info2_(info2),
info3_(info3),
bool1_(boo1),
id_(id) {
auto pos = info3_.find(info_);
if (pos == string::npos) {
bool1_ = false;
bool2_ = false;
} else if (pos == 0) {
bool2_ = true;
} else {
bool2_ = false;
}
}
bool operator < (const Item& a) {
return (rand() % 10) > 4;
}
};
struct Items {
map<string, vector<Item>>* lists_;
Items() {
lists_ = new map<string, vector<Item>>;
}
void AddItem(const string& info_,
const string& info2_,
const string& info3,
const bool boo1,
const int64_t id) {
Item item(info_, info2_, info3, boo1, id);
auto iter = lists_->find(info2_);
if (iter == lists_->end()) {
vector<Item> s;
(*lists_)[info2_] = s;
}
auto topo = &(*lists_)[info2_];
topo->emplace_back(item);
}
void Sort() {
for (auto iter = lists_->begin(); iter != lists_->end(); iter++) {
vector<Item>* s = &iter->second;
std::sort(s->begin(), s->end());
}
}
};
int main(int argc, char** argv) {
Items* a = new Items();
a->AddItem("1.com", "1-4", "1.com", true, 1);
a->AddItem("2.com", "1-4", "1.com,2.com", true, 2);
a->AddItem("3.com", "1-4", "3.com,1.com", true, 3);
a->AddItem("4.com", "2-4", "4.com", false, 10);
a->AddItem("5.com", "2-4", "1.com", true, 10);
a->AddItem("6.com", "2-4", "1.com", true, 15);
a->AddItem("7.com", "3-4", "7.com", false, 7);
a->AddItem("8.com", "3-4", "1.com", false, 8);
a->AddItem("9.com", "3-4", "1.com", false, 9);
srand((unsigned) time(NULL));
a->Sort();
return 0;
}
STL sort实现
简而言之
- 数据量大时,采用Quick Sort
- 数据量小于某个阈值时,改用Insertion Sort
- 递归层次过深时,改用Heap Sort
算法细节部分进行了优化,但原理不变
问题根源
需要阅读stl源码
此部分参考博客https://liam.page/2018/09/18/std-sort-in-STL/
std::sort
template <class RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last) {
if (first != last) {
__introsort_loop(first, last, value_type(first), __lg(last - first) * 2);
__final_insertion_sort(first, last);
}
}
他接受两个随机访问迭代器 [first, last)
,这里假设 last
不会先于 first
。
__introsort_loop
为具体的排序实现,__final_insertion_sort
则是插入排序。 std::sort
在排序基本完成任务后调用插入排序以提升效率,在__introsort_loop
排序基本完成后,frist和last也会相应改变,只剩下[first, last)
这个小区间乱序,直接插入排序即可。
__introsort_loop
template <class RandomAccessIterator, class T, class Size>
void __introsort_loop(RandomAccessIterator first,
RandomAccessIterator last, T*,
Size depth_limit) {
while (last - first > __stl_threshold) {
if (depth_limit == 0) {
partial_sort(first, last, last);
return;
}
--depth_limit;
RandomAccessIterator cut = __unguarded_partition
(first, last, T(__median(*first, *(first + (last - first) / 2),
*(last - 1))));
__introsort_loop(cut, last, value_type(first), depth_limit);
last = cut;
}
}
__stl_threshold
是一个预定义的宏,当左闭右开区间的长度不大于该超参数时,转向插入排序。
每次 __introsort_loop
的递归,参数 depth_limit
都会自减一次;当该参数为 0 时,意味着递归深度已经很深,转向堆排,并退出递归。堆排序部分,我们不再赘述
__final_insertion_sort
template <class RandomAccessIterator>
void __final_insertion_sort(RandomAccessIterator first,
RandomAccessIterator last) {
if (last - first > __stl_threshold) {
__insertion_sort(first, first + __stl_threshold);
__unguarded_insertion_sort(first + __stl_threshold, last);
} else {
__insertion_sort(first, last);
}
}
当区间长度较小时,直接调用 __insertion_sort
;当区间长度较大时,对前 __stl_threshold
个元素调用 __insertion_sort
,而对前 __stl_threshold
个元素之后的元素调用 __unguarded_insertion_sort
。 __unguarded_insertion_sort
做了某种优化以提升性能
__insertion_sort
template <class RandomAccessIterator>
void __insertion_sort(RandomAccessIterator first, RandomAccessIterator last) {
if (first == last) return;
for (RandomAccessIterator i = first + 1; i != last; ++i)
__linear_insert(first, i, value_type(first));
}
__linear_insert
template <class RandomAccessIterator, class T>
inline void __linear_insert(RandomAccessIterator first,
RandomAccessIterator last, T*) {
T value = *last;
if (value < *first) {
copy_backward(first, last, last + 1);
*first = value;
} else {
__unguarded_linear_insert(last, value);
}
}
出现问题的函数找到了
__linear_insert
函数模板的意图是将 last
所指向的元素插入到正确位置,这里蕴含的前提假设是[first, last)
区间的元素是已经排好序的。
在这一假设下,若 *last < *first
,则last
指向的元素应当插入在上述区间的最前面,因此有 std::copy_backward
;
若不满足条件判断,则在 [first, last)
之间必然存在不大于 value
的元素(比如至少 *first
是这样)
__unguarded_linear_insert
template <class RandomAccessIterator, class T>
void __unguarded_linear_insert(RandomAccessIterator last, T value) {
RandomAccessIterator next = last;
--next;
while (value < *next) {
*last = *next;
last = next;
--next;
}
*last = value;
}
next迭代器自减,直到找到比value小的元素;
它在对 next
迭代器的自减中,没有检查 next
迭代器是否向左超越边界,甚至根本没有输入左边界;
由于上一个函数的判断,此处假设应当是:while
循环会在 next
迭代器向左越界之前停止,但是如果两个元素的大小比较并不确定,而是一个随机值,此时就可能出现越界。