捕获this的Lambda转发后结合fmt::join出现段错误,求原因与解决方法
段错误原因分析与解决办法
问题重现
以下代码在运行时,执行std::cout << add << std::endl;语句时触发段错误,移除foo函数中的fmt::join后程序可正常运行:
#include <ranges> #include <map> #include <functional> #include <fmt/format.h> #include <iostream> template<typename RANGE, typename LAM> auto foo(RANGE&& range, LAM&& lambda) { return fmt::join(range | std::views::transform( [lam = std::forward<LAM>(lambda)](const auto& element) { return lam(element); }), ","); } class boo { public: int add {2}; void call_foo(){ std::vector<int> d { 2,3,4,5,6}; auto f = fmt::format("{}", foo(d, [this](int i) { std::cout << add << std::endl; return add + i; })); std::cout << f << std::endl; }; }; int main() { std::cout << "HELLO" << std::endl; boo b; b.call_foo(); }
问题原因
核心问题是悬空引用导致的未定义行为:
fmt::join返回的fmt::join_view对象会持有对输入范围的引用,而这里的输入范围是range | std::views::transform(...)生成的临时transform_view对象。- 当
foo函数返回时,这个临时的transform_view会被销毁,导致join_view内部持有的引用变成悬空状态。 - 后续
fmt::format遍历这个join_view时,会访问已经销毁的transform_view及其内部的lambda,触发非法内存访问,表现为段错误。
C++20视图的惰性求值特性放大了这个问题:transform_view的lambda直到fmt::format实际生成字符串时才会被调用,此时原视图早已失效。
解决办法
方法1:直接返回格式化后的字符串(兼容C++20,推荐)
修改foo函数,在内部完成字符串拼接并返回std::string,确保所有视图的求值都在其生命周期内完成:
template<typename RANGE, typename LAM> std::string foo(RANGE&& range, LAM&& lambda) { return fmt::format("{}", fmt::join( range | std::views::transform( [lam = std::forward<LAM>(lambda)](const auto& element) { return lam(element); } ), "," )); }
方法2:使用Owning View延长视图生命周期(C++23+)
如果编译器支持C++23,可以用std::views::owning_view包裹临时视图,让它持有视图的副本而非引用,避免悬空:
#include <ranges> // 确保包含C++23的owning_view template<typename RANGE, typename LAM> auto foo(RANGE&& range, LAM&& lambda) { auto transformed = std::views::owning_view( range | std::views::transform( [lam = std::forward<LAM>(lambda)](const auto& element) { return lam(element); } ) ); return fmt::join(transformed, ","); }
方法3:提前将视图转换为容器
先把transform_view转换为具体容器(如std::vector),再传递给fmt::join,确保容器的生命周期覆盖fmt::format的求值过程:
template<typename RANGE, typename LAM> auto foo(RANGE&& range, LAM&& lambda) { auto transformed_vec = range | std::views::transform( [lam = std::forward<LAM>(lambda)](const auto& element) { return lam(element); } ) | std::ranges::to<std::vector>(); return fmt::join(transformed_vec, ","); }
内容的提问来源于stack exchange,提问作者ATK
相关产品推荐
相关产品推荐

