向std::forward_list尾部emplace元素触发段故障,求排查
我肯定是忽略了某个细节,但该如何向std::forward_list的尾部添加元素呢?我知道通常forward_list的实现没有尾迭代器,但根据cppreference,标准std::forward_list要求提供常量时间的end()迭代器,所以我尝试使用它。
按照逻辑,emplace_after()会修改给定迭代器前一个节点的next指针,所以我需要在cend()的前一个节点后插入,即调用std::prev(mylist_.cend())。即使列表为空,这会指向begin()之前的位置,而cppreference说明该位置是被实现支持的,emplace_after()的示例也这么做,但我的代码触发了段错误:
#include <memory_resource> #include <cstdio> #include <ranges> #include <algorithm> #include <forward_list> struct entity { entity() : mylist_{ std::pmr::polymorphic_allocator<std::byte>{}} {} auto add(int i) { // printf("start = %p, end = %p", &*mylist_.cbegin(), &*mylist_.cend()); auto it = mylist_.emplace_after(std::prev(mylist_.cend())); *it = i; } auto print() { std::ranges::for_each(mylist_, [](int i){ printf("%d\n", i); }); } std::pmr::forward_list<int> mylist_; }; int main() { entity e; e.add(4); e.print(); }
我通过注释的打印语句检查了start和end迭代器,它们指向同一地址。请问我哪里出错了?
核心错误原因
std::prev要求迭代器具备双向迭代器(BidirectionalIterator)的能力,但std::forward_list的迭代器是前向迭代器(ForwardIterator),仅支持递增操作,不支持递减。直接对end()迭代器调用std::prev属于未定义行为,这就是触发段错误的根源。
cppreference中提到的"begin()之前的位置",指的是before_begin()返回的迭代器——这个迭代器专门用于在链表头部插入元素,但它和end()迭代器没有直接关联,也无法通过end()倒推得到。
正确的尾部插入方式
由于forward_list是单向链表,没有尾指针,要实现尾部插入必须遍历到链表的最后一个节点,再调用emplace_after插入元素,时间复杂度为O(n):
auto add(int i) { if (mylist_.empty()) { // 空链表时,用before_begin()在头部插入(等价于尾部插入) mylist_.emplace_after(mylist_.before_begin(), i); } else { auto it = mylist_.begin(); // 遍历到最后一个节点(下一个节点是end()) while (std::next(it) != mylist_.end()) { ++it; } mylist_.emplace_after(it, i); } }
也可以用C++20的范围库简化遍历逻辑:
#include <ranges> auto add(int i) { // 找到最后一个节点:所有元素中,下一个元素不存在的那个 auto last = std::ranges::find_if(mylist_, [](auto&&) { return false; }, std::views::drop(1)); mylist_.emplace_after(last, i); }
额外建议
如果你的场景需要频繁进行尾部插入操作,std::forward_list并不是合适的选择——因为每次尾部插入都需要遍历整个链表。建议换用:
std::list:支持O(1)时间的头尾插入,但内存开销略大std::vector:尾部插入 amortized O(1),且内存连续性更好,访问效率更高
内容的提问来源于stack exchange,提问作者glades

