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 迭代器向左越界之前停止,但是如果两个元素的大小比较并不确定,而是一个随机值,此时就可能出现越界。

posted @ 2021-03-16 11:17  Jamgun  阅读(53)  评论(0编辑  收藏  举报