右值引用是否允许悬空引用?

发布于 2024-09-19 20:06:51 字数 290 浏览 9 评论 0 原文

考虑以下内容。

#include <string>
using std::string;

string middle_name () {
    return "Jaan";
}

int main ()
{
    string&& danger = middle_name();   // ?!
    return 0;
}

这不会计算任何东西,但它编译时没有错误,并演示了一些我觉得令人困惑的东西:danger是一个悬空引用,不是吗?

Consider the below.

#include <string>
using std::string;

string middle_name () {
    return "Jaan";
}

int main ()
{
    string&& danger = middle_name();   // ?!
    return 0;
}

This doesn't compute anything, but it compiles without error and demonstrates something that I find confusing: danger is a dangling reference, isn't it?

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

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

发布评论

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

评论(4

无远思近则忧 2024-09-26 20:07:12

当然,右值引用仍然是一个引用,因此它也可以是悬空的。您只需让编译器陷入这样一种情况:他必须拖动引用,同时您只需转义引用值的范围,如下所示:

演示

#include <cstdio>
#include <tuple>

std::tuple<int&&> mytuple{ 2 };

auto pollute_stack()
{
    printf("Dumdudelei!\n");
}

int main()
{
    {
        int a = 5;
        mytuple = std::forward_as_tuple<int&&>(std::move(a));
    }
    pollute_stack();
    int b = std::get<int&&>(mytuple);
    printf("Hello b = %d!\n", b);
}

输出:

Dumdudelei!
Hello b = 0!

如您所见,b 现在的值是错误的。怎么会?我们将对自动变量 a 的右值引用填充到全局元组中。然后我们转义了 a 的范围并通过 std::get> 检索它的值;它将评估为右值引用。所以新对象 b 实际上是从 a 构造的,但是编译器找不到 a 因为它的作用域已经结束了。因此,std::get 的计算结果为 0(尽管它可能是 UB 并且可以计算为任何值)。

请注意,如果我们不接触堆栈,右值引用实际上仍然会找到对象 a 的原始值,即使它的作用域已经结束,并且会检索正确的值(只需尝试并取消注释 < code>pollut_stack() 看看会发生什么)。 pollut_stack() 函数只是向前和向后移动堆栈指针,同时通过 printf() 执行一些与 io 相关的操作,将值写入堆栈。

编译器根本看不透这一点,所以要注意这一点。

Of course, an rvalue reference is still a reference so it can be dangling as well. You just have to bring the compiler into a situation where he has to drag the reference along and at the same time you just escape the refered-to value's scope, like this:

Demo

#include <cstdio>
#include <tuple>

std::tuple<int&&> mytuple{ 2 };

auto pollute_stack()
{
    printf("Dumdudelei!\n");
}

int main()
{
    {
        int a = 5;
        mytuple = std::forward_as_tuple<int&&>(std::move(a));
    }
    pollute_stack();
    int b = std::get<int&&>(mytuple);
    printf("Hello b = %d!\n", b);
}

Output:

Dumdudelei!
Hello b = 0!

As you can see, b now has the wrong value. How come? We stuffed an rvalue reference to an automatic variable a into a global tuple. Then we escaped the scope of a and retrieve its value through std::get<int&&> which will evaluate to an rvalue-reference. So the new object b is actually move constructed from a, but the compiler doesn't find a because its scope has ended already. Therefore std::get<int&&> evaluates to 0 (although it is probably UB and could evaluate to anything).

Note that if we don't touch the stack, the rvalue reference will actually still find the original value of object a even after its scope has ended and will retrieve the right value (just try it and uncomment pollute_stack() and see what happens). The pollute_stack() function just moves the stack pointer forward and back while writing values to the stack by doing some io-related stuff through printf().

The compiler doesn't see through this though at all so be aware of this.

坏尐絯℡ 2024-09-26 20:07:09

danger 是一个悬空引用,不是吗?

与使用 const & 没什么区别:danger 取得右值的所有权。

danger is a dangling reference, isn't it?

Not any more than if you had used a const &: danger takes ownership of the rvalue.

不喜欢何必死缠烂打 2024-09-26 20:07:06

右值引用绑定到右值。右值可以是纯右值或x值[解释]。绑定到前者永远不会创建悬空引用,绑定到后者可能会创建悬空引用。这就是为什么选择 T&& 作为函数的返回类型通常是一个坏主意。 std::move 是此规则的一个例外。

T&  lvalue();
T   prvalue();
T&& xvalue();

T&& does_not_compile = lvalue();
T&& well_behaved = prvalue();
T&& problematic = xvalue();

rvalue references bind to rvalues. An rvalue is either a prvalue or an xvalue [explanation]. Binding to the former never creates a dangling reference, binding to the latter might. That's why it's generally a bad idea to choose T&& as the return type of a function. std::move is an exception to this rule.

T&  lvalue();
T   prvalue();
T&& xvalue();

T&& does_not_compile = lvalue();
T&& well_behaved = prvalue();
T&& problematic = xvalue();
你的他你的她 2024-09-26 20:07:03

右值引用允许悬空引用吗?

如果您的意思是“是否可以创建悬空右值引用”,那么答案是肯定的。然而,你的例子

string middle_name () {
    return "Jaan";
}

int main()
{
    string&& nodanger = middle_name();   // OK.
    // The life-time of the temporary is extended
    // to the life-time of the reference.
    return 0;
}

完全没问题。同样的规则也适用于 这个例子(赫伯·萨特的文章)也很安全。如果使用右值初始化引用,则临时对象的生命周期将延长到引用的生命周期。不过,您仍然可以生成悬空引用。例如,这不再安全:

int main()
{
    string&& danger = std::move(middle_name());  // dangling reference !
    return 0;
}

因为 std::move 返回一个 string&& (这不是一个< em>纯右值)延长临时生命周期的规则不适用。这里,std::move 返回一个所谓的xvaluexvalue 只是一个未命名的右值引用。因此,它可以引用任何内容,并且在不查看函数的实现的情况下基本上不可能猜测返回的引用所引用的内容。

Do rvalue references allow dangling references?

If you meant "Is it possible to create dangling rvalue references" then the answer is yes. Your example, however,

string middle_name () {
    return "Jaan";
}

int main()
{
    string&& nodanger = middle_name();   // OK.
    // The life-time of the temporary is extended
    // to the life-time of the reference.
    return 0;
}

is perfectly fine. The same rule applies here that makes this example (article by Herb Sutter) safe as well. If you initialize a reference with a pure rvalue, the life-time of the tempoary object gets extended to the life-time of the reference. You can still produce dangling references, though. For example, this is not safe anymore:

int main()
{
    string&& danger = std::move(middle_name());  // dangling reference !
    return 0;
}

Because std::move returns a string&& (which is not a pure rvalue) the rule that extends the temporary's life-time doesn't apply. Here, std::move returns a so-called xvalue. An xvalue is just an unnamed rvalue reference. As such it could refer to anything and it is basically impossible to guess what a returned reference refers to without looking at the function's implementation.

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