跳到主要内容C++ 深入解析 std::back_inserter 用法与原理 | 极客日志C++算法
C++ 深入解析 std::back_inserter 用法与原理
std::back_inserter 是 C++ 标准库中的迭代器适配器,配合 algorithm 算法将元素追加至容器尾部。其底层通过调用容器的 push_back 方法实现。使用时需注意容器需支持 push_back,避免源与目标为同一容器导致未定义行为。对于 vector 等容器,建议预先 reserve 优化性能。结合 make_move_iterator 可实现移动语义优化。
林间仙子2 浏览 概览:是什么、为什么要它
- 是什么:
std::back_inserter 是一个函数模板,返回一个 std::back_insert_iterator<Container>(一个迭代器适配器)。
- 作用:把一个'支持
push_back 的容器'的尾部包装成一个输出迭代器(output iterator),可以把元素'写入'容器末尾。这样你就可以把容器当作 std::copy、std::transform 等算法的目标,不必显式地 push_back 每个元素。
- 典型用途:与
<algorithm> 组合,将某个范围的元素追加到容器末尾(无需自己写循环)。
标准签名(粗略):
template <class Container> std::back_insert_iterator<Container> back_inserter(Container& c);
调用后可得到一个可以被赋值的迭代器:*it = value 会触发 c.push_back(value)。
为什么比手写循环好?
- 代码更简洁、可读:
std::copy(src.begin(), src.end(), std::back_inserter(dst)) 比显式循环更 declarative。
- 与标准算法配合流畅(例如
std::transform、std::copy_if、std::remove_copy 等)。
- 把构造容器结果的责任从算法搬到容器,算法只负责'产生元素'。
使用示例(基础)
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
int main() {
std::vector<int> src{1, 2, 3, 4};
std::vector<int> dst;
std::copy(src.begin(), src.(), std::(dst));
( x : dst) {
std::cout << x << ;
}
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
end
back_inserter
for
int
' '
for (auto &x : src) {
dst.push_back(x);
}
std::back_insert_iterator 的工作机制(内部原理简述)
back_insert_iterator<Container> 是一个小类模板,典型结构如下(伪代码):
template <class Container> class back_insert_iterator {
public:
explicit back_insert_iterator(Container& c) : container(&c) {}
back_insert_iterator& operator=(const typename Container::value_type& value) {
container->push_back(value);
return *this;
}
private:
Container* container;
};
std::back_inserter(c) 只是创建并返回这样一个 back_insert_iterator<Container>(c)。
关键点:给这个迭代器赋值(*it = v 或算法内部的 *out = element)会调用 container.push_back(element)。
与算法配合的例子(常见且实用)
std::vector<int> evens;
std::copy_if(src.begin(), src.end(), std::back_inserter(evens), [](int x){ return x % 2 == 0; });
std::transform(把字符串转成长度列表):
std::vector<std::string> names = {"alice", "bob", "charlie"};
std::vector<size_t> lengths;
std::transform(names.begin(), names.end(), std::back_inserter(lengths), [](const std::string& s){ return s.size(); });
与 std::make_move_iterator 联用(把元素移动到目标容器):
std::vector<std::string> a = {"a", "b", "c"};
std::vector<std::string> b;
std::copy(std::make_move_iterator(a.begin()), std::make_move_iterator(a.end()), std::back_inserter(b));
与 std::inserter / std::front_inserter 的比较
std::back_inserter 使用 container.push_back(x):适用于 vector, deque, list(这些支持 push_back)。
std::front_inserter 使用 push_front:适用于 deque, list 等支持 push_front 的容器。
std::inserter(container, it) 在任意位置插入:使用 container.insert(it, x),需要一个插入位置迭代器(适用于关联容器或任何支持 insert 的容器)。
- 追加末尾 →
back_inserter
- 插头前端 →
front_inserter
- 指定位置或关联容器 →
inserter
容器要求与复杂度考量
back_inserter 要求容器有 push_back(value)(并且 value_type 与要插入类型可兼容)。
- 复杂度:每次插入复杂度由容器决定:
std::vector:摊销常数时间(amortized O(1)),但若触发容量扩展,会发生 reallocation(O(n))。
std::deque / std::list:通常 O(1)。
- 性能提示:若你知道要插入的元素数量,最好先对
vector 调用 reserve(n),以避免多次扩容带来的内存重分配开销。
dst.reserve(src.size());
std::copy(src.begin(), src.end(), std::back_inserter(dst));
与移动语义配合(避免不必要拷贝)
- 当
src 的元素可移动且你想移动它们到目标容器,使用 std::make_move_iterator:
std::copy(std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()), std::back_inserter(dst));
这样 operator= 调用将使用移动构造/移动赋值,从而减少拷贝。
常见坑与注意事项
- 不可用于不支持
push_back 的容器:例如 std::set、std::map 没有 push_back,所以不能与 back_inserter 一起用。对这些容器应使用 std::inserter。
- 迭代器失效问题:当
std::vector 扩容时,原来对 vector 持有的引用/指针/迭代器会被 invalidated。如果你的算法同时以某种方式依赖被插入 vector 的旧迭代器,注意可能产生问题。但通常,算法使用 back_inserter 只是写入,不会读取目标容器的旧迭代器。
- 当源与目标是同一个容器时要小心:把容器的范围复制到自身的末尾(例如
std::copy(v.begin(), v.end(), std::back_inserter(v)))是未定义行为(UB),因为你在改变容器的同时还在读取原范围,会导致无限增长或迭代器失效。要先复制到临时容器或使用算法专门支持重叠的情况(通常不存在通用解决法),例如:
auto tmp = v;
std::copy(tmp.begin(), tmp.end(), std::back_inserter(v));
-
std::bind / lambda 捕获导致额外拷贝:当通过 back_inserter 在算法中插入复杂对象,注意算法或传入的可调用是否会导致不必要拷贝,必要时使用移动语义或 emplace_back。
-
与 emplace_back 的关系:back_inserter 调用的是 push_back(value);若你想在目标容器中'直接构造'元素(避免先构造再拷贝/移动),可以考虑把算法输出配合 emplace_back ——但标准并没有 emplace_back_iterator。常见替代是把生产工作放进 lambda,并直接 container.emplace_back(args...) 在外部循环中调用,或使用 transform 生成构造完的对象然后 back_inserter 插入(但那会产生先构造临时再移动/拷贝的开销)。现代 C++ 中更常用的做法是把生成逻辑写成接受构造参数并在容器端 emplace_back。
自己实现一个简单的 back_inserter(示例代码)
template<typename Container> class my_back_insert_iterator {
public:
using container_type = Container;
explicit my_back_insert_iterator(Container& c) : container(&c) {}
my_back_insert_iterator& operator=(const typename Container::value_type& value) {
container->push_back(value);
return *this;
}
my_back_insert_iterator& operator*() { return *this; }
my_back_insert_iterator& operator++() { return *this; }
my_back_insert_iterator& operator++(int) { return *this; }
private:
Container* container;
};
template<typename Container> my_back_insert_iterator<Container> my_back_inserter(Container& c) {
return my_back_insert_iterator<Container>(c);
}
这与标准的 std::back_insert_iterator 概念一致:赋值就把值 push_back 到容器。
进阶场景与技巧
- 用
std::back_inserter 收集 std::transform 的输出(链式):
std::vector<int> a = {1, 2, 3};
std::vector<int> b, c;
std::transform(a.begin(), a.end(), std::back_inserter(b), [](int x){ return x*2; });
std::transform(b.begin(), b.end(), std::back_inserter(c), [](int x){ return x+1; });
std::vector<int> dst = {100, 101};
std::copy(src.begin(), src.end(), std::back_inserter(dst));
- 和
std::move_iterator 联合用于零拷贝传递(已示例)。
- 与
std::ostream_iterator 做对比:std::ostream_iterator 也是输出迭代器,但它把输出写入流(而不是容器)。它的 operator= 行为是把值格式化输出到流。两者都可作为算法的目标。
常见错误示例(以及改正)
std::vector<int> v = {1, 2, 3};
std::copy(v.begin(), v.end(), std::back_inserter(v));
auto tmp = v;
std::copy(tmp.begin(), tmp.end(), std::back_inserter(v));
错误示例:对不支持 push_back 的容器使用 back_inserter
std::set<int> s;
std::vector<int> v = {1, 2, 3};
std::copy(v.begin(), v.end(), std::back_inserter(s));
改正:使用 std::inserter(s, s.end()) 或 std::inserter(s, s.begin())。
小结(要点速查)
std::back_inserter(container) 返回一个输出迭代器,赋值会调用 container.push_back(value)。
- 常与标准算法(
std::copy, std::transform, std::copy_if 等)配合,用于把生成的元素追加到容器末尾。
- 容器必须支持
push_back。对 vector,在大量插入前最好 reserve。
- 想移动元素时配合
std::make_move_iterator 使用。
- 切忌对同一容器进行源到尾部的复制(会引发 UB 或逻辑错误)。
- 若希望按指定位置插入或对关联容器插入,使用
std::inserter 或 std::front_inserter(视情形而定)。