【问题标题】:const reference to temporary oddityconst 对临时怪异的引用
【发布时间】:2010-06-22 23:11:14
【问题描述】:

我们都知道这样的事情在 c++ 中是有效的:

const T &x = T();

同时:

T &x = T();

不是。

a recent question 中的对话导致了这条规则。 OP 发布了一些明显唤起 UB 的代码。但我希望它的修改版本可以工作(这是修改后的版本):

#include <iostream>
using namespace std;

class A {
public:
    A(int k) { _k = k; };
    int get() const { return _k; };
    int _k;
};

class B {
public:
    B(const A& a) : _a(a) {}
    void b() { cout << _a.get(); }
    const A& _a;
};

B* f() {
    return new B(A(10));
}

int main() {
    f()->b();
}

这会在某些机器上打印垃圾,在其他机器上打印 10... 对我来说听起来像 UB :-)。但后来我想,A 基本上是一个美化的int 它所做的一切都是初始化一个并阅读它。为什么不直接打电话给Aint 看看会发生什么:

#include <iostream>
using namespace std;

typedef int A;

class B {
public:
    B(const A& a) : _a(a) {}
    void b() { cout << _a; }
    const A& _a;
};

B* f() {
    return new B(A(10));
}

int main() {
    f()->b();
}

它每次都打印10。至少似乎 const 引用规则对int 版本有效,但对类版本无效。由于使用堆,它们都只是UB吗?我对int 版本很幸运,因为编译看到了所有consts 并直接打印出10?我错过了规则的哪一方面?

【问题讨论】:

    标签: c++ reference constants temporary


    【解决方案1】:

    它只是表明通过“在编译器中尝试”来分析语言行为通常不会产生任何有用的结果。由于同样的原因,您的两个示例都无效。

    仅当您将该临时对象用作 const 引用的直接初始化程序时,才会延长临时对象的生命周期 - 只有这样才能在引用和临时对象之间建立“生命周期”链接。

    尝试将临时变量作为构造函数的参数传递并在构造函数内部附加一个 const 引用不会建立上述链接,也不会延长临时变量的生命周期。

    另外,按照 C++ 标准,如果你这样做

    struct S {
      const int &r;
    
      S() : r(5) {
        cout << r; // OK
      }
    };
    

    临时的生命周期只延长到构造函数的末尾。一旦构造函数完成,临时的就死掉了,这意味着这个

    S s;
    cout << s.r; // Invalid
    

    无效。

    您对int 的实验只是“似乎有效”,纯属偶然。

    【讨论】:

    • 我建议您将“a const reference”更改为“a local const reference”以使事情更加清晰;)
    • @FredOverflow:你为什么坚持使用 local 参考?您可以在命名空间范围内合法声明 const std::string&amp; r = "Hello" 并获得相同的生命周期延长。
    • @AndreyT,这个链接是如何建立在什么级别的?
    • 哦,我忘了全局变量。我猜“一个 non-member const 引用”是我真正的意思:)
    • 它可能会起作用,因为编译器优化了不存在的引用,只是复制了 int。
    【解决方案2】:

    你很幸运。将 B::b 更改为:

    void b() {
        int i = rand();
        int j = rand();
        cout << _a << endl;
    }
    

    打印出随机数。

    【讨论】:

      【解决方案3】:

      每次打印 10 个。

      稍微修改一下main函数,就不再打印10了:

      int main()
      {
          B* p = f();
          cout << "C++\n";   // prints C++
          p->b();            // prints 4077568
      }
      

      这个链接是如何建立在什么级别的?

      参见 12.2 [class.temporary] §4 和 §5:

      临时对象在评估完整表达式的最后一步被销毁,完整表达式(词法上)包含它们的创建点。

      在两种情况下,临时对象在与完整表达式结尾不同的点被销毁。 第一个上下文是 [...]

      第二个上下文是引用绑定到临时的。引用绑定到的临时对象或引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,除非:[...]

      在函数调用中临时绑定到引用参数会一直存在,直到包含调用的完整表达式完成为止。

      因此,在您的情况下,在评估完整表达式 new B(A(10)) 后,临时被销毁。

      【讨论】:

        猜你喜欢
        • 2010-10-20
        • 2015-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-02
        • 2023-03-30
        • 1970-01-01
        相关资源
        最近更新 更多