为什么在初始化列表中进行自我引用初始化不会出错?

3

我遇到了一个奇怪的问题。当我尝试编译自我分配对象引用时,我的编译器(针对ESP32)不会显示任何错误或警告。我调查了这个问题,并发现一些编译器不会为这段代码显示任何错误或警告:

#include <iostream>
#include <string>

class Foo
{
    public:
        std::string s;
        Foo(){ std::cout << "Foo()\n"; }
        std::string ToString() { return s; }
};

class Bar
{
    public:
        Foo& foo;
        Bar(): foo(foo) { std::cout << "Bar()\n"; }
        std::string ToString() { return foo.ToString(); }
};

int main()
{
    Bar bar;
    std::cout << "START\n" << bar.foo.ToString() << "\nEND\n";
}

只有clang会显示有关自赋值的警告,但即使如此,它显然应该是一个错误。是否存在某种情况,使得这种行为合法?


对我来说,与GCC一起工作(在这里)(“工作”是指:它显示了有关自我分配和未初始化引用的警告)。 - user9212993
法律上?当然。但它几乎肯定也会导致未定义的行为。 - Caninonos
@TheDude 但是为什么这不是一个错误呢?例如,编译器不允许以同样的方式使用指针。 - Logman
为什么会有人踩这个问题呢?这个问题难道不是写得很好吗,包括一个示例和对期望和观察到的行为的描述? - Bathsheba
我认为你可以将整个代码简化为这样:int& x = x; 一个引用指向它自己。 - Nikos C.
显示剩余5条评论
3个回答

4

这种行为是否有合法的情况?

没有。[dcl.ref]/5规定:

引用必须初始化为指向有效的对象或函数。

您的示例涉及以一种不指向有效对象的方式初始化引用,因此它是不合法的。通常出于明显的原因,这并不总是可能诊断...但在这里,很明显这是一个错误,这就是为什么gccclang都会发出警告的原因。

只有clang会显示有关自我分配的警告,但即使如此,这显然应该是一个错误。

标准实际上并不涉及什么是警告和什么是错误。它只涉及诊断。为什么这些编译器选择将其诊断为警告而不是错误?无从得知。

无论如何,这是一个容易纠正的问题。如果你想要一个错误,请使用-Werror编译。现在,在gccclang上都会出现错误。这通常也是一个好习惯。


标准并不要求对于引用自初始化进行诊断,这是一个QoI问题。通常情况下,无法在编译时检测所有情况;尽管如此,对于编译器可以捕获的简单情况发出警告似乎是个好主意。 - M.M
@M.M 你能想到编译器无法追溯潜在对象引用的任何情况吗? - Logman
@Barry 在这种情况下,您明确告诉编译器,您通过间接引用拥有一个有效的指针对象。 - Logman
@Logman 是的,全局 int& x = foo();,其中 foo 做一些复杂或不确定的事情,以决定是返回 x 还是另一个静态 int。 - M.M
@M.M 你黑客可能的问题 ;). 凭直觉,我认为这是正确的。但我试图找到单个例子,但我找不到。 - Logman

1
请注意,编译器拒绝为标准未规定的内容生成可执行文件是不合法的(与未定义相反),这将导致编译器无法默认使用-Werror。编译器还必须证明您的程序的每个可能的执行路径都会调用此未定义行为,这通常是不可能的。标准没有说您的代码是有问题的,只是说在调用此构造函数时是未定义的。现在你可能会问,为什么标准不把它声明为有问题的,因为它显然是不正确的?原因是复杂性。标准已经很庞大了。有一些情况下,标准可以识别出在编译时可以证明的无条件未定义行为的子集,并将其声明为有问题的,但这样做将是一个非常大的工作量,并且会给已经很大的文档增加更多特殊情况。因此,让编译器在这些情况下产生警告并为用户提供选择将这些警告变成错误是更实际的选择。

0

因为自初始化引用是未定义的行为,所以编译器可以诊断消息或不诊断。

[basic.life]/2说:

引用的生命周期从其初始化完成时开始。

因此,foo的生命周期不会在初始化期间开始。 然后,根据[expr.type]/1(重点是我的):

如果一个表达式最初具有“对T的引用”的类型([dcl.ref],[dcl.init.ref]),则在进一步分析之前将该类型调整为T。 该表达式指定由引用指示的对象或函数,并且该表达式是一个左值或xvalue,具体取决于表达式。 [注意:在引用的生命周期开始之前或结束之后,行为是未定义的(参见[basic.life])。—结束注释]

在初始化表达式中调整foo的类型会导致未定义的行为。


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