重新审视 C++ 中的生命周期扩展,我发现有一些模式破坏了 C++ 表达式的“可分解性”。例如,以下两个 block 是 valid C++ code :
class NonMovable {
public:
NonMovable(NonMovable&&) = delete;
NonMovable(const NonMovable&) = delete;
NonMovable();
int Value() const;
};
template <class T>
const T& identity(const T& x) {
return x;
}
template <class T>
class A {
public:
explicit A(const T& value) : value_(value) {}
const T& GetValue() const {
return value_;
}
private:
const T& value_;
};
正确用法:
int main() {
int retcode = identity(
identity(/*tmp1*/ A(/*tmp2*/ NonMovable{}).GetValue())).Value();
// tmp1 and tmp2 end their lifetimes here:
// their full-expression is the whole previous line
return retcode;
}
但是如果我们分解main
中的第一个表达式,它就会变得无效:
int main() {
auto&& a_obj = /*tmp1*/ A(/*tmp2*/ NonMovable{});
// tmp2 lifetime ends here
// oops! dereferencing dangling reference:
int retcode = identity(
identity(a_obj.GetValue())).Value();
return retcode;
// tmp1 lifetime ends here
}
我的问题是: 是否可以禁用第二种用法?
P.S.:我不太确定第二个 main
是否引入了 UB,因为我已经 tested使用clang -Wlifetime
,它不会提示。但我还是相信是UB。在现实生活中,我遇到过类似的行为:如果我将单个表达式分解为两个单独的表达式,代码就会损坏,发出 UBSan 警告和段错误。
P.P.S.:如果我正确理解对象的生命周期(我现在对此表示怀疑),那些身份
实际上并不重要
最佳答案
你的分析是正确的。如果没有生命周期扩展,所有临时变量都会在“完整表达式”的末尾被销毁,即行末尾的 ;
。所以当你说
int retcode = A(NonMovable{}).GetValue().Value();
(为了清楚起见,删除了评论和身份调用)然后一切都好;当您请求其值时,NonMovable
对象仍然存在。
另一方面,当你说
auto&& a_obj = A(NonMovable{});
然后 NonMovable
在行尾被销毁,并且 A
对象将持有一个悬空引用。 (顺便说一句,auto&&
只是在这里延长了临时A
的生命周期——您也可以只使用普通的auto
)
My question is: Is it possible to disable the second kind of usage?
事实并非如此,至少据我所知。您可以添加已删除的 A(NonMovable&&)
构造函数,但这也会阻止第一个示例中的“正确”使用。这与 std::string_view
出现的问题完全相同(并且在 C++20 中也会出现 std::span
)——本质上,您的
类具有引用语义,但引用的是已被销毁的临时对象。
关于c++ - 防弹 C++ 临时生命周期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58166210/