为何std::views::transform组合调用会产生异常输出?
问题:为何View0的输出与Created和View1不一致?
以下是测试代码:
#include <iostream> #include <vector> #include <ranges> struct S { float x = 150.f; S f() const { return *this; } }; int main() { std::vector<S> vec{ { 1.f }, { 2.f } }; std::cout << "\nCreated\n"; for ( const auto& l : vec ) { std::cout << l.f().x << ' '; } std::cout << "\nView0\n"; for ( float t : vec | std::views::transform( &S::f ) | std::views::transform( &S::x ) ) { std::cout << t << ' '; } std::cout << "\nView1\n"; auto view1 = vec | std::views::transform( &S::f ) | std::views::transform( [] ( const S& l ) { return l.x; } ); for ( float t : view1 ) { std::cout << t << ' '; } }
开启优化的Clang和GCC运行后输出:
Created 1 2 View0 0 0 View1 1 2
GCC开启-Wall -Wextra -pedantic-errors时的警告:
<source>:33:52: warning: using a dangling pointer to an unnamed temporary [-Wdangling-pointer=] 33 | | std::views::transform( &S::x ) | ^ In file included from <source>:3: /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/ranges:1949:54: note: unnamed temporary defined here 1949 | { return std::__invoke(*_M_parent->_M_fun, *_M_current); } <source>:33:52: warning: '<unnamed>.S::x' may be used uninitialized [-Wmaybe-uninitialized] 33 | | std::views::transform( &S::x ) | ^ /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/ranges:1949:54: note: '<anonymous>' declared here 1949 | { return std::__invoke(*_M_parent->_M_fun, *_M_current); }
原因分析
View0的问题:临时对象悬空引发未定义行为
- 第一个
std::views::transform(&S::f)会对容器中的每个S对象调用f(),而f()的返回类型是S(值返回),因此每次调用都会生成一个临时S对象。 - 第二个
std::views::transform(&S::x)使用成员指针&S::x来访问成员。此时transform内部的逻辑是先获取临时对象的地址,再通过成员指针读取x——但这个临时对象在第一个transform的调用完成后就会被立即销毁,导致后续访问的是悬空指针指向的内存,这属于C++标准明确的未定义行为,输出结果完全不可预测(可能是0、垃圾值甚至程序崩溃)。
为什么Created和View1能正常工作?
- Created区块:循环中
l.f().x是直接在表达式中访问临时对象的成员,C++规则会将临时对象的生命周期延长到整个表达式结束(即std::cout调用完成),因此能正确读取到值。 - View1区块:第二个
transform使用lambda表达式[] (const S& l) { return l.x; },lambda接收的是临时对象的常量引用。根据C++规则,绑定到常量引用的临时对象生命周期会延长到引用的生命周期结束(即lambda函数调用期间),因此可以安全访问l.x,不会出现悬空问题。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

