C++ - 使用常量引用来延长临时对象的成员,是合法的还是未定义的行为?

37

考虑类似于这样的东西:

#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 A& a = A();
const B& b = a.b;

这是完全合法的,但我正在努力找出为什么同样的机制/规则不适用于任何类型的临时情况。

未来参考编辑:

我使用的是gcc版本6.3.0


我不知道你使用哪个编译器/工具链。我已经使用C++2a +最新的CLang(HEAD)进行了测试,似乎可以正常工作-> https://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`。 - mutantkeyboard
1
gcc 6.3.0(这是我办公室可用的版本) - user2717954
2
@mutantkeyboard 编译无错误只意味着其语法正确,但不代表其有效。同样,运行_"无"_错误也不能保证其有效性,UB 表示程序可能在不产生任何错误消息的情况下运行并得到预期结果,但仍然是 UB,因此程序将无效。 - t.niese
@t.niese 我完全同意你的观点。然而,这不是重点。我更感兴趣的是看看在这种情况下不同的编译器/工具链如何表现,因为我发现这是一种有趣的行为。这就是为什么我要求他给我GCC/CLANG版本的原因 :) 我正在研究编译器内部,所以这是一个有趣的测试项目。 - mutantkeyboard
1
@mutantkeyboard 但是[...]并且似乎工作正常[...]这句话真的很误导,因为它暗示你认为它是有效的,只是因为它编译通过并且没有出现任何错误消息。这个问题不能通过测试是否编译通过来回答(除非你知道一个编译器的编译设置会精确地警告可能由于const ref引起的UB)。 - t.niese
2个回答

34

你的代码应该是良好的形式,因为对于临时对象而言

(强调是我的)

每当引用被绑定到一个临时对象或其子对象时,该临时对象的生命周期将被扩展以匹配引用的生命周期

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

使用 -O2 选项在 clang10 上实时运行
使用 -O2 选项在 gcc10 上实时运行

顺便说一句:这似乎是一个已经修复的 gcc 错误

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

The third context is when a reference is bound to a temporary object.36 The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:

...

[ Example:

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

— end example ]


1
参考信息:相关的标准章节为 http://eel.is/c++draft/class.temporary#6。 - Max Langhof
那么这是我的gcc版本中的一个错误吗? - user2717954
1
作为一条说明:它在gcc 8.2和clang 4.0中可行,在这些版本之前,它在gcc和clang中是有问题的。 - t.niese
1
@t.niese 是的,这似乎是gcc的bug,已经得到修复。 - songyuanyao
2
@user2717954 我想我找到了 - songyuanyao

1

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

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

1
gcc 8.2clang 4.0 起,仅写入 const B&b = A()。b [4]; 可以延长 AB 的生命周期。 - t.niese
7
如果另一个答案(以及它引用的标准)是正确的,那么这个答案就是错误的。 - 463035818_is_not_a_number

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接