#include <iostream>
#include <vector>
#include <cstdint>
#include <optional>
using GroupId = std::uint64_t;
using ReducedGroupId = GroupId;
struct Memo {
std::optional<ReducedGroupId> GetReduceGroupId(const GroupId& group_id) {
// omit
return std::make_optional<ReducedGroupId>(group_id);
}
};
int main(int argc, char* argv[]) {
std::vector<GroupId> tmp;
Memo memo;
GroupId group_id = 1;
const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value();
std::cout << "3.1.->|" << reduced_group_id << ":" << &reduced_group_id << std::endl;
tmp.push_back(4);
std::cout << "3.2.->|" << reduced_group_id << ":" << &reduced_group_id << std::endl;
tmp.push_back(5);
std::cout << "3.3.->|" << reduced_group_id << ":" << &reduced_group_id << std::endl;
return 0;
}
3.1.->|1:0x7ffe4fcd3530
3.2.->|4:0x7ffe4fcd3530
3.3.->|5:0x7ffe4fcd3530
1
zhouxiaoyuan 107 天前 via Android
不是 c++神奇,是没管理好对象的生命周期,不能引用临时变量 GetReduceGroupId 。
const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value(); |
2
Betsy OP @zhouxiaoyuan GetReduceGroupId() 返回值不用 optional 修饰,却又没问题。
|
3
Donaldo 107 天前 2
```c++
const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value(); ``` 华点在这一行,memo.GetReduceGroupId(group_id)是个临时的右值,你取完就悬垂了,所以这个这时候他的值是个 UB 。具体是什么全看编译器实现: - Apple M2 aarch64+ clang16: 1, 1, 1 - Windows x86 + msvc14: 1, 1, 1 - Linux x86 + gcc14: 1, 4, 5 想要避免 UB ,多加一行把这个临时值存起来就好了 ```c++ auto rgi = memo.GetReduceGroupId(group_id); const ReducedGroupId& reduced_group_id = rgi.value(); ``` 这里 auto 类型可以是 std::optional<ReducedGroupId>也可以是它的右值引用 std::optional<ReducedGroupId> &&。 |
4
chrisyunhua 107 天前 via Android
去掉 & 改为 `const ReducedGroupId id = ...` 也可以。
@Betsy 不用 optional 没问题,我的理解是:因为 GetReducedGroupId() 属于 prvalue ,赋给 const & 时触发了 lifetime extension ;而 optional.value() 返回类型是 const & 属于 lvalue ,不触发 lifetime extension 。 https://en.cppreference.com/w/cpp/language/lifetime |
5
noahlias 107 天前
我怎么觉得是你的编译器问题 或者编译 option 的问题
https://godbolt.org/z/4b6x9WKaE 我看这里返回结果是一样的 ``` ASM generation compiler returned: 0 Execution build compiler returned: 0 Program returned: 0 3.1.->|1:0x7ffdce1d3300 3.2.->|1:0x7ffdce1d3300 3.3.->|1:0x7ffdce1d3300 ``` |
6
blinue 107 天前 1
未定义行为就是编译器怎么做都可以,有一篇很好的博客 https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633
|
8
bfjm 107 天前 via iPhone
这里应该主要的问题是 optional 被析构了,你还在拿他的一个成员变量的引用,所以出现了垂悬引用,这里的解决方案可以参考前面几楼的答案,还可以提升 optional 的返回值的生命周期
|
10
InkStone 107 天前
试了一下编译时候有 warning 的……都不用开-Wall
test.cpp:21:46: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl] const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value(); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 写 C++的时候-Wall -Werror 是个好习惯 |
11
rabbbit 107 天前
visual studio 输出的都是 1
这么写行吗? const ReducedGroupId&& reduced_group_id = std::move(memo.GetReduceGroupId(group_id).value()); |
12
rabbbit 107 天前
额数值有必要用引用吗?
|
13
fighterhit 107 天前
人生苦短,学点别的吧
|
14
MoYi123 107 天前
编译有警告
main.cpp:43:46: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl] const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value(); 运行有 asan ==7760==ERROR: AddressSanitizer: stack-use-after-scope on address 0x00016ba93320 at pc 0x00010436df8c bp 0x00016ba93250 sp 0x00016ba93248 READ of size 8 at 0x00016ba93320 thread T0 #0 0x10436df88 in main+0x610 (a.out:arm64+0x100001f88) #1 0x187dbe0dc (<unknown module>) Address 0x00016ba93320 is located in stack of thread T0 at offset 192 in frame #0 0x10436d984 in main+0xc (a.out:arm64+0x100001984) This frame has 7 object(s): [32, 40) 'ref.tmp.i.i124' [64, 72) 'ref.tmp.i.i103' [96, 104) 'ref.tmp.i.i' [128, 152) 'tmp' [192, 208) 'ref.tmp' <== Memory access at offset 192 is inside this variable [224, 232) 'ref.tmp15' [256, 264) 'ref.tmp28' HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: stack-use-after-scope (a.out:arm64+0x100001f88) in main+0x610 |
16
sanbuks 107 天前
template< class T >constexpr std::optional<std::decay_t<T>> make_optional( T&& value );
引用被 decay 了,直接赋值接收就行了 |
17
araraloren 107 天前
Very cool feature, thanks for share this.
|
18
a554340466 107 天前
读一下 C++17 The complete guide 的 optional 那一章
|
19
a554340466 107 天前
it is always safe to assign any optional return value to a new object:
auto a = getString().value(); // OK: copy of contained object or exception However, using the returned value directly (other than passing it as an argument) is a source of trouble: ```cpp auto b = *getString(); // ERROR: undefined behavior if std::nullopt const auto& r1 = getString().value(); // ERROR: reference to deleted contained object auto&& r2 = getString().value(); // ERROR: reference to deleted contained object ``` The problem with the references is that by rule, they extend the lifetime of the return value of value() but not the lifetime of the optional object returned by getString(). Thus, r1 and r2 refer to values that no longer exist and using them results in undefined behavior. |
21
ipwx 107 天前
额,楼主你这
std::optional<ReducedGroupId> GetReduceGroupId(const GroupId& group_id) { // omit return std::make_optional<ReducedGroupId>(group_id); } 不是取了 group_id 的地址塞到 optional 里面。optional 本来就是个完整的对象,所以你是复制了一份 group_id 塞到了 optional 里面。 然后 const ReducedGroupId& reduced_group_id 取的就是这个临时的 optional 内部的 int64 的地址,当然这句话执行完就被 “销毁” 了。后面的代码都是错的。 |
22
ipwx 107 天前 1
optional 类似于
template <typename T> struct Optional { T* myObject; Optional() : myObject(nullptr) {} Optional(const T& value) : myObject(new T(value)) {} ~Optional() { delete myObject; } bool has_value() { return myObject != nullptr; } } |
23
Betsy OP |
24
Betsy OP @a554340466 这个地址可以发我不?原始出处没找到
|
26
realJamespond 107 天前
右值是个很抽象的东西一直没搞清楚
|
27
rabbbit 107 天前
摘自 C++Primer plus
使用引用参数的主要原因有两个。 程序员能够修改调用函数中的数据对象。 通过传递引用而不是整个数据对象,可以提高程序的运行速度。 当数据对象较大时(如结构和类对象),第二个原因最重要。这些 也是使用指针参数的原因。这是有道理的,因为引用参数实际上是基于 指针的代码的另一个接口。那么,什么时候应使用引用、什么时候应使 用指针呢?什么时候应按值传递呢?下面是一些指导原则: 对于使用传递的值而不作修改的函数。 如果数据对象很小,如内置数据类型或小型结构,则按值传递。 既然 std::move 是不行的。 根据我的理解,你是希望修改 group_id 时,跟着变动 reduced_group_id ,对吗? |
28
blinue 106 天前 1
我在 godbolt 里复现这个问题: https://godbolt.org/z/h45896sM5
只会在 O1 优化下出现,是一个悬垂引用导致的巧合。 1. memo.GetReduceGroupId(group_id) 返回的临时的 std::optional<ReducedGroupId> 存储在 [rsp + 16] 到 [rsp + 24]。optional 本身共 9 个字节,前 8 个字节是 ReducedGroupId ,后跟一个 bool 。 2. reduced_group_id 为 .value() 返回的地址,即 rsp + 16 ,注意这是一个栈上的临时空间,reduced_group_id 为悬垂引用。后续用 rbx 存储 reduced_group_id 引用的地址。 3. tmp.push_back(4) 恰好将 4 ( 8 个字节)存储到 [rsp + 16],覆盖了 reduced_group_id 指向的内存。编译器认为这是安全的,因为临时的 std::optional<ReducedGroupId> 已经析构,这导致后续读取 reduced_group_id 的值为 4 。 |
29
a554340466 106 天前 1
@Betsy https://dokumen.pub/c17-the-complete-guide-396730017x-9783967300178.html
|