对临时异常的 const 引用

发布于 2024-09-07 00:10:54 字数 1338 浏览 4 评论 0原文

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

const T &x = T();

而:则

T &x = T();

不是。

最近的问题中,对话引出了这条规则。 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 所有它所做的就是初始化一个并读取它。为什么不直接将 A 调用为 int 并看看会发生什么:

#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 版本,因为编译看到了所有 const 并直接打印出了 10 吗?我缺少规则的哪一方面?

We all know that things like this are valid in c++:

const T &x = T();

while:

T &x = T();

is not.

In a recent question the conversation lead to this rule. The OP had posted some code which clearly evokes UB. But I would have expect a modified version of it to work (This is the modified version):

#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();
}

This prints garbage on some machines, 10 on others... sounds like UB to me :-). But then I thought, well A is basically a glorified int all it does it initialize one and read it. Why not just call A an int and see what happens:

#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();
}

It prints 10 every time. It at least seems like the const reference rule is in effect for the int version, but not for the class version. Are they both simply UB due to the use of the heap? Am I just lucky with the int version because the compile saw through all consts and just directly printed out a 10? Which aspect of the rule am I missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

未央 2024-09-14 00:10:54

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

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

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

另外,根据 C++ 标准,如果执行此操作,

struct S {
  const int &r;

  S() : r(5) {
    cout << r; // OK
  }
};

临时变量的生命周期只会延长到构造函数的末尾。一旦构造函数完成,临时变量就会消失,这意味着这

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

是无效的。

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

It simply demonstrates that analyzing language behavior by "trying it in the compiler" doesn't normally produce any useful results. Both of your examples are invalid for the very same reason.

The lifetime of the temporary is only extended when you use that temporary as the direct initializer for a const reference - only that will establish a "lifetime" link between the reference and the temporary.

Trying to pass a temporary as a constructor's argument and attaching a const reference inside the constructor will not establish the aforementioned link and will not extend the lifetime of the temporary.

Also, in accordance with C++ standard, if you do this

struct S {
  const int &r;

  S() : r(5) {
    cout << r; // OK
  }
};

the lifetime of the temporary is only extended to the end of the constructor. Once the constructor is finished, the temporary dies, meaning that this

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

is invalid.

Your experiment with int simply "appears to work", purely by accident.

凉城 2024-09-14 00:10:54

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

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

打印出随机数。

You just got lucky. Changing B::b to this:

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

prints out random numbers.

鱼忆七猫命九 2024-09-14 00:10:54

每次打印 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)) 后临时对象将被销毁。

It prints 10 every time.

Modify the main function a little and it won't print 10 anymore:

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

how does this link gets established at what level?

See 12.2 [class.temporary] §4 and §5:

Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created.

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression.
The first context is [...]

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: [...]

A temporary bound to a reference parameter in a function call persists until the completion of the full-expression containing the call.

So in your case, the temporary is destroyed after the evaluation of the full-expression new B(A(10)).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文