c++ 11本地命名引用返回值(xvalue)

C++11 local named references to return value (xvalue)?

本文关键字:返回值 xvalue 引用 11本 c++      更新时间:2023-10-16

我们有以下类型X和函数f:

struct X { ... };
X f() { ... };

现在考虑另一个函数g的三个可选定义:

(1)

void g()
{
    X x = f();
    ...
}

(2)

void g()
{
    X& x = f();
    ...
}

(3)

void g()
{
    X&& x = f();
    ...
}

在三种不同的情况下,定义行为(或潜在行为)的区别是什么?(假设占位符"……"三种情况下的代码是相同的)

更新:

如果g返回一个X:下面是合法的和正确的吗?

X g()
{
    X&& x = f();
    ...
    return move(x);
}

(移动是必要的,它做什么吗?)

您是否期望RVO链,以便下面的代码产生相同的代码?

X g()
{
    X x = f();
    ...
    return x;
}

2是非法的。其他两个基本相同——xX类型的可变左值,其生命周期在g()返回时结束。

当然,严格地说,第一种调用move构造函数(但它是一些RVO/NRVO操作的主要候选),而第三种不调用,所以如果X是不可移动的(非常奇怪……),第三种情况是合法的,但第一种情况不是,并且第一种情况可能更昂贵。然而,编译器选项和不可移动类型的实际情况是,这几乎完全是一个技术问题,如果您能实际演示任何这样的情况,我会感到惊讶。

表达式f()按值返回,所以它是prvalue

  1. 创建一个类型为X的新对象,初始化为表达式f(),这是一个类型为X的右值。如何构造新的X对象取决于它是否有move构造函数。如果它有一个移动构造函数(或一个接受X右值的模板构造函数),它将被调用,否则如果它有一个复制构造函数,它将被调用。实际上,编译器几乎肯定会忽略构造函数调用,但是适当的构造函数必须是可访问的,并且不能被删除。

  2. 这甚至没有编译,你在发布之前没有尝试过,你会得到一个反对票!

  3. 这将创建一个类型为X&&的新引用,并通过将其绑定到f()返回的右值来初始化它。

  4. 右值的生存期将延长到与引用x相同的生存期。

行为上的区别可能没有什么,假设省略了move/copy,但是(1)和(3)之间在语义上有区别,因为一个对构造函数进行重载解析,这可能会失败,而另一个总是工作。

如果g返回一个X,下面的操作合法且正确吗?

这是合法的。move是不必要的,有一个特殊的规则说,当按值返回一个局部变量时,构造函数查找首先完成,好像变量是一个右值,所以如果X有一个移动构造函数,它将被使用,无论你是否使用move(x)。RVO应该"连锁"。如果g返回X&,那么在这两种情况下都有问题,因为它绑定的对象将在g结束时超出作用域。

(注意:

始终限定std::move以防止ADL是一种良好的做法。std::forward也是如此。如果你想调用std::movestd::forward,那么要显式,不要依赖于范围内或可见的没有过载,moveforward不是像swap那样的自定义点。


与其通过问关于SO的问题来学习c++,为什么不写代码来测试会发生什么并向自己证明呢?在g++中,您可以使用-fno-elide-constructors标志来关闭构造函数省略,以查看没有省略时会发生什么,当不使用该标志(默认值)时,您可以轻松地为自己测试RVO是否"链"。