C++ - 使用 const 引用来延长一个临时的、好的或 UB 的成员?

Posted

技术标签:

【中文标题】C++ - 使用 const 引用来延长一个临时的、好的或 UB 的成员?【英文标题】:C++ - using const reference to prolong a member of a temporary, ok or UB? 【发布时间】:2020-01-11 22:18:45 【问题描述】:

考虑这样的事情:

#include <iostream>

struct C 
    C(double x=0, double y=0): x(x) , y(y) 
        std::cout << "C ctor " << x << " " <<y << " "  << "\n";
    
    double x, y;
;

struct B 
    B(double x=0, double y=0): x(x), y(y) 
    double x, y;
;

struct A 
    B b[12];

    A() 
        b[2] = B(2.5, 14);
        b[4] = B(56.32,11.99);
    
;


int main() 
    const B& b = A().b[4];
    C c(b.x, b.y);

当我使用 -O0 编译时,我得到了打印结果

C ctor 56.32 11.99

但是当我使用 -O2 编译时,我得到了

 C ctor 0 0

我知道我们可以使用 const 引用来延长本地临时,所以像

const A& a = A();
const B& b = a.b;

完全合法。但我正在努力寻找为什么相同的机制/规则不适用于任何临时的原因

编辑以供将来参考:

我使用的是 gcc 6.3.0 版

【问题讨论】:

我不知道您使用的是哪个编译器/工具链。我已经用 C++2a + 最新的 CLang (HEAD) 对此进行了测试,似乎工作正常 -> wandbox.org/permlink/CNRZzNSXlD4NQUNg 并且你可以看到发出的命令是:clang++ prog.cc -Wall -Wextra -O2 -march=native -I/opt/wandbox/boost-1.71.0/clang-head/include -std=gnu++2a -pedantic gcc 6.3.0(这是我办公室可用的版本) @mutantkeyboard 编译没有错误仅仅意味着它在语法上是正确的。这并不意味着它是有效的。并且运行 "without" 错误也不意味着它是有效的,UB 意味着它可以在没有任何错误消息产生预期结果的情况下运行,但它仍然是 UB,因此程序将无效. @t.niese 完全同意你的看法。这不是重点。我更感兴趣的是不同的编译器/工具链在这种情况下的行为方式,因为我发现这是一种有趣的行为。这就是为什么我要求他给我 GCC/CLANG 版本 :) 我正在对编译器内部进行一些研究,所以这是一种有趣的测试。 @mutantkeyboard 但是[...]and seems to work fine[...] 确实具有误导性,因为它暗示您认为它是有效的,只是因为它可以编译并且您没有收到任何错误消息。如果编译时没有任何错误消息,则无法通过测试来回答该问题。 (除非你知道一个编译器的编译器设置会因为 const ref 而对可能的 UB 完全失去兴趣)。 【参考方案1】:

您的代码应该格式正确,因为对于temporaries

(强调我的)

每当引用绑定到临时对象或子对象时,临时对象的生命周期就会延长以匹配引用的生命周期

给定A().b[4]b[4]b的子对象,数据成员b是临时数组A()的子对象,应该延长其生命周期。

LIVE on clang10 with -O2LIVE on gcc10 with -O2

顺便说一句:这似乎是 gcc 的 bug 已修复。

来自标准,[class.temporary]/6

第三个上下文是引用绑定到临时对象时。36 引用绑定到的临时对象或作为子对象的完整对象的临时对象如果引用绑定到的泛左值是通过以下方式之一获得的,则绑定的引用将在引用的生命周期内持续存在:

...

[示例:

template<typename T> using id = T;

int i = 1;
int&& a = id<int[3]>1, 2, 3[i];          // temporary array has same lifetime as a
const int& b = static_cast<const int&>(0); // temporary int has same lifetime as b
int&& c = cond ? id<int[3]>1, 2, 3[i] : static_cast<int&&>(0);
                                           // exactly one of the two temporaries is lifetime-extended

—结束示例]

【讨论】:

供参考:相关标准部分为eel.is/c++draft/class.temporary#6 这是我的 gcc 版本中的错误吗? 注意:它从 gcc 8.2 和 clang 4.0 开始工作,在这些版本之前它在 gcc 和 clang 中被破坏。 @t.niese 是的,这似乎是 gcc 的 bug 已修复。 @user2717954 我想我找到了it。【参考方案2】:

A().b[4] 不是 temp 或 rvalue,这就是它不起作用的原因。虽然A() 是临时的,但您正在创建对创建时存在的数组元素的引用。 dtor 然后触发A(),这意味着稍后对b.b 的访问变得有些未定义的行为。您需要保留 A&amp; 以确保 b 保持有效。

const A& a = A();
const B& b = a.b[4];
C c(b.x, b.y);

【讨论】:

至少从 gcc 8.2clang 4.0 开始,AB 的生命周期都是延长的,只写 const B&amp; b = A().b[4]; 如果另一个答案(以及它所制定的标准中的引用)是正确的,那么这个答案是错误的

以上是关于C++ - 使用 const 引用来延长一个临时的、好的或 UB 的成员?的主要内容,如果未能解决你的问题,请参考以下文章

临时变量不能作为非const引用

为啥我们可以非常量引用临时对象并延长其生命周期?

C++“警告:返回对临时的引用”——但事实并非如此

右值引用,移动语义,完美转发

为啥在通过 const 引用传递临时值时调用复制构造函数?

C++中的const关键字深入理解(关于引用指针顶层const)