问题描述
struct A{
const std::string& _s;
A(const std::string& s):_s(s){}
//A(const std::string s): _s(s){}
};
int main(void){
A a("E");
std::cout << a._s << '\n';
}
默认情况下,这根本不会出错或发出警告,您可以通过使用 asan 进行编译,使其在运行时抛出错误。根据我的理解,"E
绑定到的临时 s
在第一个表达式结束时死亡,这意味着 A a("E")
因此类中的引用在此之后无效。因此,在 cout <<
点,我们正在访问一个悬空引用。
在编译时,如果我们将第一个构造函数替换为第二个构造函数,则会生成警告,它将引用绑定到临时 s
,因此现在在 {{1} 之后访问 s
} 表达式结束也是UB。
假设两种情况都是正确的,是否存在这样的情况
A a("E")
会是UB吗?如果是这样,什么时候?
解决方法
c++17 standard §15.2 点 6.9 说
- 在函数调用 (8.5.1.2) 中绑定到引用参数的临时对象一直存在,直到包含调用的完整表达式完成
所以你很好。此处没有 UB auto x= A("E")._s
。
您自己描述的其他问题。建设完成后访问会员为UB。
但是,您可以通过将临时对象绑定到本地常量引用来延长其生命周期,如下所示:
struct A {
const std::string& _s;
A(const std::string& s) : _s(s) {}
};
int main(void) {
const std::string& e = "E";
A a(e);
std::cout << a._s << '\n';
}
这不会是 UB,但我不会像这样使用它。
,以下是一些简单的测试代码,可准确打印各种对象的生命周期何时开始和停止:
struct loud {
loud(const char*) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
loud(const loud&) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
~loud() { std::cout << __PRETTY_FUNCTION__ << '\n'; }
};
struct A {
const loud& _s;
// A(const loud& s) : _s(s) {}
A(loud s) : _s(s) {}
};
int main() {
loud x = A("E")._s;
}
输出:
loud::loud(const char *)
loud::loud(const loud &)
loud::~loud()
loud::~loud()
并且如您所见,使用 loud
构造的 "E"
对象在复制之前不会被销毁。
在这两种情况下,都会创建一个临时对象并将其绑定到 const loud& s
或 loud s
(构造函数的参数)。所有临时对象在包含它们的完整表达式结束时被销毁,因此它们将在整个 A("E")._s
内保持活动状态,因此可以从中复制。
这也意味着以下内容不起作用,因为临时变量是在不同的完整表达式中创建的:
// The following is a hard compile time error on clang,but compiles on gcc
struct A {
const loud& _s;
A(loud s) : _s(s) {}
A(const char* s) : _s(s) /* Temporary `loud` used to initialize `_s` is destroyed here */ {}
};
// But this compiles
struct A {
const loud& _s;
A(loud s) : _s(s) {}
A(const char* s) : A(loud(s)) /* Temporary materialized from `loud(s)` is destroyed here */ {}
};
临时对象在构造函数体的 {
之前被销毁(以及任何后续数据成员的构造函数之前,如果有的话),所以它已经在 .
之前被销毁了 {{ 1}}。
在这两种情况下,使用相同的 A("E")._s
函数输出以下内容:
main
(对象被销毁后使用的地方,如果复制构造函数试图访问对象上的任何数据成员,就像它是一个 loud::loud(const char *)
loud::~loud()
loud::loud(const loud &)
loud::~loud()
)