当年我们写 STL。
容器就是容器。
算法就是算法。
你 sort 完。
你就真的得到一个排好序的容器。
所以很多人第一次见到 view。
会下意识把它当成“新容器”。
然后线上就啪一下。
当年:中间结果是“真东西”
你过滤一遍。
就得到一个新的 vector。
你 transform 一遍。
又得到一个新的 vector。
它们啰嗦。
但也有一个好处。
生命周期特别清楚。
线上啪一下:view 指向了一个已经死掉的容器
你写了个小工具。
想把一段数据过滤一下。
你顺手写成这样。
#include <ranges>
#include <vector>
auto r = std::vector{1, 2, 3, 4}
| std::views::filter([](int x) { return x % 2 == 0; });
int sum = 0;
for (int x : r) sum += x;
这段代码看起来很干净。
但它有个致命点。
std::vector{...} 是临时对象。
这一行结束。
它就销毁了。
而 r 里的 view。
只是拿着一个“指向原数据的引用”。
原数据没了。
view 还在。
这就是悬空引用。
有时候你会崩。
有时候你会算出一个离谱的 sum。
最烦。
因为它不是每次都炸。
view 的本质:它不存数据,它存“规则”
view 更像一个滤镜。
滤镜不保存照片。
它只保存。
“怎么调色”。
你把它盖在原图上。
原图还在。
你就能看到效果。
原图没了。
你就只能看到空气。
正确写法 1:把底层容器先活下来
#include <ranges>
#include <vector>
std::vector v{1, 2, 3, 4};
auto r = v | std::views::filter([](int x) { return x % 2 == 0; });
int sum = 0;
for (int x : r) sum += x;
这时候。
view 引用的是 v。
v 活着。
一切都正常。
正确写法 2:让 view 自己“拥有”底层数据
如果你的数据本来就是临时的。
你可以把它交给 view 管。
#include <ranges>
#include <vector>
auto r = std::views::all(std::vector{1, 2, 3, 4})
| std::views::filter([](int x) { return x % 2 == 0; });
int sum = 0;
for (int x : r) sum += x;
std::views::all 会尽力把 range 包装成一个“可 view 的东西”。
对临时对象。
它会倾向于用拥有语义把它包住。
这样你就不会悬空。
关键结论
view 省的不是“代码行数”。
它省的是。
你不用反复造中间容器。
但代价是。
你必须盯紧底层数据的生命周期。
小结
把 view 当滤镜。
别当新照片。
你就很少会踩坑。