在此之前,我一直假设临时对象在包含该对象的 full-expression 的结尾处被销毁。我最近遇到了规范的[class.temporary] / 5部分,该部分讨论了将临时对象分配给引用时发生的异常。在大多数情况下,这似乎总是会延长临时变量的寿命,除非在[class.temporary]/5中有一种特殊情况:
- 在构造函数的ctor-initializer(12.6.2)中临时绑定到参考成员的行为会一直存在,直到构造函数退出。
- 在函数调用(5.2.2)中与参考参数的临时绑定一直持续到包含该调用的完整表达式完成。
如果我正在阅读此书,则表明如果构造函数引用该对象,则该对象的寿命可能会短于 full-expression 。
一个案例研究:
struct A
{
A(int);
~A();
};
struct B
{
B(const A& a)
: memberRef(a)
{ }
~B();
const A& memberRef;
};
// Just a few operators to use in my full expression
int operator+(const A&, const A&);
int operator+(const A&, const B&);
void testCase()
{
int case1 = A(1) + A(2);
int case2 = A(3) + B(A(4));
}
我给每个A
构造函数一个不同的参数,以使其易于引用创建为 A1 , A2 , A3的临时变量和 BA4 。
在case1中,以任意顺序构造 A1 和 A2 ,然后进行加法运算。 A1 和 A2 的寿命受函数调用参考参数的规则支配。它们被扩展为包含加法运算符的完整表达式。
必须以相反的顺序调用析构函数,也必须使用[class.temporary] / 5:
如果两个或两个以上绑定了参考的临时对象的生存期在同一点结束,则这些临时对象 在那一刻,其破坏的顺序与完成过程相反。
所以这告诉我这些对象的析构函数必须在 A1 , A2 或 A2 , A1 <中调用/ em>,具体取决于编译器选择构造对象的顺序。到目前为止一切顺利。
对我来说,麻烦的案例是case2。如果我没看错,因为 A4 绑定到传递给B::B(const A&)
的引用上,那么它的寿命现在在该构造函数的末尾,即而不是表达式的末尾< / strong>。
这对我来说意味着析构函数可以称为 A4 , A3 , BA4 或 A4 , BA4 , A3 。但是, A4 的析构函数必须始终优先出现,因为它发生在 BA4 的构造函数的末尾,而不是在完整表达式的末尾。
这表明不可能以 A3 , BA4 , A4 的顺序调用析构函数,因为 A4 < / em>的寿命需要缩短。
我是否正确阅读了规范?如果是,那么此规则的依据是什么?对我而言,使传递给构造函数的临时函数与传递给函数调用的临时函数一样活着似乎很自然,但是看来规范编写者已经在努力制定其他规则。
答案 0 :(得分:2)
您在两者之间使用了错误的项目符号。
与构造函数的 ctor-initializer ([class.base.init])中的引用成员的临时绑定一直存在,直到构造函数退出。
不适用于此处。在 ctor-initializer 中,我们没有与参考成员的临时绑定。这种情况更像是:
struct B
{
B()
: memberRef(A(2)) // <==
{ }
~B();
const A& memberRef;
};
我们的情况恰好是this one:
在函数调用([expr.call])中与参考参数的临时绑定一直持续到包含调用的 full-expression 完成。
我们在构造函数中有一个临时(A(4)
绑定到引用参数(构造函数调用仍然是函数调用,我们要绑定到的参数是{{1中的a
}}),因此临时文件将一直保留到 full-expression 完成。
换句话说,您所显示的内容中没有悬挂的引用。关于将临时绑定到引用的所有这些规则都是关于生存期 extension 的。它们都不能缩短寿命。