为什么在初始化列表中自我初始化引用不是错误?

时间:2018-02-06 18:07:05

标签: c++ language-lawyer

我遇到了一个奇怪的问题。如果我尝试编译自助对象引用,我的编译器(对于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会显示有关自我分配的警告,但即便如此,显然也应该是错误。这种行为是否合法?

3 个答案:

答案 0 :(得分:4)

  

这种行为是否合法?

没有。 [dcl.ref]/5州:

  

应初始化引用以引用有效的对象或函数。

您的示例涉及以引用有效对象的方式初始化引用,因此它的格式不正确。一般而言,这并不总是可以通过明显的原因进行诊断......但是在这里,很明显这是一个错误,这就是为什么gccclang都会对此提出警告。

  

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

标准并没有真正处理什么是警告以及什么是错误。它只涉及诊断。为什么这些编译器选择将其诊断为警告而不是错误?耸肩。

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

答案 1 :(得分:0)

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

[basic.life]/2

  

引用的生命周期在初始化时开始   完整。

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

  

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

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

答案 2 :(得分:0)

请注意,编译器拒绝为标准未提及的内容生成可执行文件(与未定义相反)在技术上是不符合的,这就是为什么编译器不能默认为{{1 }}。编译器还必须证明程序的每个可能的执行路径都会调用这种未定义的行为,这通常是不可能的。

标准并未说明您的代码格式错误,只是未定义如果您调用此构造函数

现在你可能会说,为什么标准没有宣布这是不正确的,因为它显然是不正确的?原因是复杂性。标准已经很大了。在很多情况下,标准可以识别无条件未定义行为的子集,这些子集在编译时可以证明并使它们格式不正确,但这样做将是一项非常大的任务并且添加更多特殊内容案件到已经很大的文件。

因此,在这些情况下让编译器产生警告并让用户选择将这些警告变成错误是更实际的选择。