同一语句中先以const引用使用再移动捕获变量的编译器行为差异问询
解答:同一语句中const引用使用与移动捕获的编译器差异
这事儿本质上是GCC 6.x版本的实现bug,不是未定义行为,也不是C++标准变更导致的。咱们一步步理清楚:
先看你的复现代码
#include <iostream> #include <string> struct A { template <class F> void call (F f) { f(); } }; A test(const std::string& s) { std::cout << "in method: " << s << std::endl; return {}; } int main() { std::string s = "hello"; test(s).call([s = std::move(s)] { std::cout << "in lambda: " << s << std::endl;}); return 0; }
为什么不同编译器输出不一样?
各版本表现
- GCC 6.3:
in method:是空的,lambda里输出hello - GCC 7.2+、Clang:两处都输出
hello - 拆成两句调用时(先存
test(s)的结果,再调用call),所有编译器都输出正确的hello
问题的核心在同一语句中表达式的求值顺序上:
在你的一行调用代码里,有两个关键操作要执行:
- 调用
test(s),传入s的const引用 - 构造作为
call参数的lambda,完成s = std::move(s)的捕获初始化(这一步才会把s的内容移走)
标准规则与编译器实现差异
- C++17之前:对于
test(s).call(...)这种成员函数调用,标准并没有强制规定test(s)(对象表达式)和call的参数(lambda)谁先求值,属于未指定行为——理论上两种顺序都合法,但大多数编译器会选择更符合直觉的顺序:先执行test(s),再构造lambda。 - GCC 6.x偏偏反着来:它先构造lambda并完成移动捕获,把
s的内容移空了,再去调用test(s),自然输出空字符串。这属于实现上的偏差,在GCC 7之后已经被修复,和Clang保持了一致的、符合直觉的行为。 - C++17及之后:标准明确了求值顺序:成员函数调用
E1.E2(args)中,必须先求值E1(也就是test(s)),再求值args(也就是lambda),所以行为完全确定,不会再有差异。
对你实际场景的建议
你提到实际是异步调用场景,建议直接把代码拆分成两句:
auto result = test(s); result.call([s = std::move(s)] { /* 回调逻辑 */ });
这样不管编译器版本、C++标准版本,都能保证test(s)先执行,输出正确的s值,之后再移动捕获,完全规避求值顺序的问题。
内容的提问来源于stack exchange,提问作者Frédéric Samier
相关产品推荐
相关产品推荐

