[英]About binding a const reference to a sub-object of a temporary
像这样的代码
#include <stdio.h>
struct P2d {
double x, y;
P2d(double x, double y) : x(x), y(y) {}
~P2d() { printf("Destructor called\n"); }
};
P2d center() {
return P2d(10, 10);
}
int main(int argc, const char *argv[]) {
const double& x = center().x;
printf("x = %.18g\n", x);
return 0;
}
g++
(版本5.2.0)会破坏P2d
进入前临时的实例printf
在main
,但该值将被保存无论如何(即,而不是结合x
到临时的实际构件P2d
实例它将创建另一个临时double
到复制成员的价值)。
clang++
(IMO正确)的临时的寿命,而不是延伸P2d
实例的寿命x
基准和析构函数将在后故名printf
在main
。
如果不是使用普通double
作为x
和y
成员的类型,而是创建一个类(例如Double
),那么两个编译器都会同意,并且它们将临时P2d
对象的生命周期延长到超过printf
。
这是g++
的错误还是标准允许的错误?
CWG 1651涵盖了这一点:
问题616和1213的解决方案使应用于纯右值的成员访问或下标表达式的结果成为 xvalue,这意味着将引用绑定到临时对象的此类子对象不会延长临时对象的生命周期。 12.2 [class.temporary] 应该修改以确保它确实如此。
现状是只有 prvalues 被视为引用临时对象- 因此[class.temporary]/5 ( “第二个上下文是当引用绑定到临时对象时。” )被认为不适用。 不过,Clang 和 GCC 并未实际实施问题 616 的解决方案。 center().x
被两者视为纯右值。 我最好的猜测:
GCC 根本没有对任何 DR 做出反应。 使用标量子对象时,它不会延长生命周期,因为[dcl.init.ref]/(5.2.1.1) †未涵盖这些。 所以完整的临时对象不需要继续存在(参见aschelper 的回答),它不需要,因为引用不直接绑定。 如果子对象是类或数组类型,则引用直接绑定,GCC 会延长临时对象的生命周期。 这已在DR 60297 中注明。
Clang 识别成员访问并实现了“新的”生命周期扩展规则——它甚至处理强制转换。 从技术上讲,这与其处理值类别的方式不一致。 但是,一旦上述 DR 得到解决,这将是更明智的做法并且将是正确的行为。
因此,我会说 GCC 按当前的措辞是正确的,但当前的措辞有缺陷且含糊不清,并且 Clang 已经对 DR 1651(即N3918)实施了悬而未决的决议。 这篇论文非常清楚地涵盖了这个例子:
如果
E1
是临时表达式并且E2
不指定位字段,则E1.E2
是临时表达式。
根据论文 [expr.call]/11 的措辞, center()
是一个临时表达式。 因此,其在上述 [class.temporary] /5 中修改后的措辞适用:
第二个上下文是当引用不直接绑定(8.5.3 dcl.init.ref)或用临时表达式初始化时(第 5 条)。 相应的临时对象(如果有)在引用的生命周期内持续存在,除了: [...不适用的异常...]
瞧,我们可以延长使用寿命。 注意“对应的临时对象”不够明确,是提案延期的原因之一; 一旦修订,它肯定会被采纳。
†
是 xvalue(但不是位域)、类纯右值、数组纯右值或函数左值,并且“
cv1 T1
”与“cv2 T2
”引用兼容,或 […]
事实上,GCC 完全尊重这一点,如果子对象具有数组类型,它将延长生命周期。
我会争论 g++ 中的错误,因为引用草案 N3242 §12.2/5:
第二个上下文是引用绑定到临时对象时。 引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,除了:
所以它的寿命必须延长,除非:
临时绑定到构造函数的 ctor-initializer [..] 中的引用成员
在函数调用中临时绑定到引用参数 [..]
临时绑定到函数返回语句中返回值的生命周期 [..]
临时绑定到
new-initializer
的引用 [..]
我们的案例不符合这些例外中的任何一个,因此它必须遵循规则。 我会说 g++ 在这里是错误的。
然后,关于从同一个草案 §8.5.3/5(强调我的)中提出的引用 aschepler:
对类型“ cv1
T1
”的引用由类型为“ cv2T2
”的表达式初始化,如下所示:
如果引用是左值引用和初始化表达式
一种。 是左值(但不是位域)并且“ cv1
T1
”与“ cv2T2
”引用兼容,或湾有一个类类型...
然后 ...
否则,该引用应为对非易失性 const 类型的左值引用(即cv1应为
const
),或者该引用应为右值引用。一种。 如果初始化表达式
一世。 是 xvalue 、类纯右值、数组纯右值或函数左值,并且“ cv1
T1
”与“ cv2T2
”引用兼容,或ii. 有一个类类型...
那么在第一种情况下,引用绑定到初始化表达式的值......
湾否则,将使用非引用复制初始化 (8.5) 的规则从初始值设定项表达式创建和初始化类型为“ cv1
T1
”的临时对象。 然后将引用绑定到临时对象。
看看 xvalue 是什么,这次引用http://en.cppreference.com/w/cpp/language/value_category ...
xvalue(“到期值”)表达式是 [..]
am
,对象表达式的成员,其中 a 是右值, m 是非引用类型的非静态数据成员;
...表达式center().x
应该是一个 xvalue,因此第 8.5.3/5 节中的案例 2a 适用(而不是副本)。 我会坚持我的建议:g++ 是错误的。
只需阅读科伦坡的回答。
这是一个 gcc 错误。 相关规则在[class.temporary] 中:
在两种情况下,临时对象在与完整表达式结束不同的点被销毁。 [...]
第二个上下文是引用绑定到临时对象时。 引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,除了:
— 在函数调用 (5.2.2) 中绑定到引用参数的临时对象一直存在,直到包含调用的完整表达式完成。
— 函数返回语句(6.6.3)中的返回值的临时绑定的生命周期没有延长; 临时在 return 语句中的完整表达式结束时被销毁。
— 对new-initializer (5.3.4) 中引用的临时绑定一直存在,直到包含new-initializer的完整表达式完成。
我们将引用绑定到临时对象的子对象,因此临时对象应该在引用的生命周期内持续存在。 此规则的这三个例外均不适用于此。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.