C++的一个典型生命周期例子 (reference)

@vrqq  July 13, 2023

Section1 Reference的生命周期

先来看这段代码

int get1(int &x) { return x; }
const int& get2(int &x) { return x; }

int main() {
  const int &u = get1(9);   //OK
  print(u);  //OK
  const int &t = get2(12);  //dangling reference here.
  print(t);  //UB

  print(get2(333));  //OK
}

为什么print(t)会UB呢?
这里面12是个临时值,超出这行就失效了。

可常听说 const int& 会延长生命周期,为什么这里不延长了?
C++标准规定,只有直接引用某个instance 或者这个instance内的成员,才会保持其生命周期。经过函数的reference 只能传递地址 无法传递生命周期(not transitive
the lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

为什么u的生命周期被延长了?区别是啥?
get1()没有返回reference,所以 "数字9的生命周期" 是绑定在u上的
get2()返回的是数字12的reference,而C++标准规定:经过函数return的reference无法延长其"绑定范围",也就是数字12无法绑定到t上,在离开这行代码后,12的生命周期就自动结束了。对比最后一行的用法,print结束后 333的生命周期才结束,这样在print里面可以安全使用333这个数字。

Reference
其他几种用法 另请参照
https://www.albertogramaglia.com/cpp-temporary-lifetime-extension-rvo/

Section2 fmt v10.0.0

在fmt release V10.0.0版本中,下述文件在GCC会报possibly dangling reference to a temporary.
https://github.com/fmtlib/fmt/blob/a0b8a92e3d1532361c2f7feb63babc5c18d00ef2/include/fmt/core.h#L1674
他的实际调用过程如下图:
微信图片_20230713001329.png

其中FMT_FORWARD(val) => std::move(val)
1.L1674 auto&& arg = arg_mapper<Context>().map(FMT_FORWARD(val));
2.L1461 return do_map(std::forward<T>(val));
3.L1445 FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T&

是bug吗?实际上没问题,但是这种写法用法欺骗了编译器,在1674行传入了"右值val",并拿到了一个val的引用。问题就在这个move,让编译器认为 "val是临时值,不该返回对它的引用"。(事实上val的生命周期在L1673行持有 并不会出现UB,图中代码的arg只用到了指针功能 没有用到生命周期延长功能,在这个make_value()返回时构造了一个新的element)
可以认为是这种写法 不符合c++语法惯例,欺骗了编译器。。

而这个问题随着这个commit被修复了
https://github.com/fmtlib/fmt/commit/ef55d4f52ec527668a8e910a56ea79d9b939dbc2

Section3

下一章来填C++的各种RValue PRValue的演变,在不同版本的c++上,其“明确的语法意义”有大的变动,神奇的是并不影响向下兼容。


添加新评论