函数传对象引用与创建本地引用变量的差异及段错误问题
直接说结论:这两种方式存在致命差异,问题出在std::unique_ptr返回的临时对象的生命周期管理上,你的本地引用写法会导致悬垂引用,进而触发未定义行为(比如你遇到的崩溃)。
让我们拆解两种场景的代码逻辑:
场景1:直接将克隆对象通过引用传入函数
假设你的代码是这样的:
void someFunction(OthelloGameState& gameState) { gameState.makeMove(...); } // 调用处 someFunction(*state.clone());
这里state.clone()返回的是一个临时的std::unique_ptr<OthelloGameState>。当你把*temp_ptr作为函数参数传递时,C++的规则会延长这个临时unique_ptr的生命周期,直到整个函数调用完成。也就是说,在someFunction执行期间,unique_ptr还活着,它指向的OthelloGameState对象是有效的,调用makeMove()不会有问题。
场景2:创建本地引用变量赋值给克隆对象
你的代码大概是这样的:
auto& localRef = *state.clone(); localRef.makeMove(...); // 这里出问题!
这里的问题非常关键:state.clone()返回的临时unique_ptr是一个右值,而你用auto&(左值引用)去绑定它解引用后的结果。C++中,只有const T&或者T&&(右值引用)才能延长临时对象的生命周期,普通的左值引用auto&做不到。
这意味着,在这条赋值语句结束后,临时的unique_ptr会立即被销毁——而unique_ptr的销毁会自动释放它指向的OthelloGameState对象。此时localRef就变成了悬垂引用,指向已经被释放的内存。后续调用localRef.makeMove()时,你在操作一块已经不属于你的内存,这必然会导致未定义行为(段错误、崩溃都是典型表现)。
正确的本地引用用法
如果一定要用本地引用,你需要先把克隆出来的unique_ptr保存到一个左值变量中,让它的生命周期覆盖你使用引用的整个过程:
auto clonedState = state.clone(); // 保存unique_ptr到本地变量 auto& localRef = *clonedState; localRef.makeMove(...); // 现在没问题了,对象还活着
这样clonedState的生命周期是当前作用域,它指向的对象会一直存在到clonedState被销毁,localRef也不会变成悬垂引用。
关于const对象调用makeMove()的问题
你提到makeMove()是非const函数,在const对象上调用会触发段错误——这其实是编译器会先报错的问题(除非你用了const_cast强制转换)。如果真的出现了段错误,大概率是你通过某种方式绕过了const检查,导致在一个实际上是const的对象上修改它的状态,这同样属于未定义行为。而你后来的本地引用问题,本质上是悬垂引用导致的,和const对象的问题是两个不同的错误场景。
内容的提问来源于stack exchange,提问作者Enryu

