4

我们有以下类型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;
}
4

2 回答 2

6

2是违法的。其他两个本质上是相同x的 - 是类型的可变左值,X其生命在g()返回时结束。

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

于 2013-01-07T00:08:17.877 回答
1

表达式f()按值返回,因此它是prvalue

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

  2. 这甚至无法编译,您会因为在发布前没有尝试而遭到否决!

  3. 这将创建一个新的类型引用,X&&并通过将其绑定到返回的纯右值来初始化它f()。该纯右值的生命周期将延长到与引用相同的生命周期x

假设移动/复制被省略,行为上的差异可能没什么,但是(1)和(3)之间的语义存在差异,因为一个为构造函数做了重载决议,这可能会失败,而另一个总是有效。

如果 g 返回一个 X 怎么办:以下是合法且正确的吗?

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

(注意,始终有资格std::move阻止 ADL 是一种很好的做法。对于. 也是如此std::forward。如果您的意思是调用std::movestd::forward然后是显式的,请不要依赖范围内没有重载或可见,move并且forward不是自定义点,如swap.)


与其通过提出关于 SO 的问题来学习 C++,不如编写代码来测试发生的事情并向自己证明呢?使用 G++,您可以使用该-fno-elide-constructors标志关闭构造函数省略以查看在没有省略的情况下会发生什么,并且在不使用该标志(默认)时,您可以轻松测试 RVO 是否为自己“链接”。

于 2013-01-07T22:27:49.170 回答