范围结束时的左值是否可以视为右值?

时间:2017-07-19 01:21:55

标签: c++ c++14 lvalue-to-rvalue

编辑:请考虑以下两个示例:

std::string x;
{
    std::string y = "extremely long text ...";
    ...
    x = y; // *** (1)
}
do_something_with(x);
struct Y
{
    Y();
    Y(const Y&);
    Y(Y&&);
    ... // many "heavy" members
};

struct X
{
    X(Y y) : y_(std::move(y)) { }
    Y y_;
}

X foo()
{
    Y y;
    ...
    return y; // *** (2)
}

在两个例子中,第(1)和(2)行中的y已接近其生命周期并即将被销毁。很明显,它可以被视为右值并在两种情况下都可以移动。在(1)中,其内容可以移入x和(2)中X().y_的临时实例。

我的问题是:

1)是否会在上述任何一个例子中移动?     (a)如果是,则采用何种标准规定。     (b)如果不是,为什么不呢?这是标准中的遗漏还是我没有想到的其他原因?

2)如果上述答案为否。在第一个例子中,我可以将(1)更改为x = std::move(y)以强制编译器执行移动。在第二个示例中,我可以做什么来向编译器指示可以移动yreturn std::move(y)

注意:我故意在(2)中返回Y而不是X的实例以避免(N)RVO。

2 个答案:

答案 0 :(得分:8)

第一个例子

对于你的第一个例子,答案显然是“不”。该标准允许编译器在处理函数的返回值时对副本(即使有副作用)采取各种自由。我想,在std::string的特定情况下,编译器可以“知道”复制和移动都没有任何副作用,因此它可以在as-if规则下用一个代替另一个。但是,如果我们有类似的东西:

struct foo {
    foo(foo const &f) { std::cout << "copy ctor\n"; }
    foo(foo &&f) { std::cout << "move ctor\n"; }
};

foo outer;
{ 
    foo inner;
    // ...
    outer = inner;
}

......正常运行的编译器必须生成打印出“copy ctor”的代码,而不是“move ctor”。实际上没有具体的引用 - 相反,有引用讨论函数返回值的异常,这里不适用,因为我们没有处理函数的返回值。

至于为什么没有人处理这个问题:我猜这只是因为没人感到困扰。从函数中返回值经常发生,因此值得花费大量精力来优化它。创建一个非功能块,并在块中创建一个值,该块将继续复制到块外部的值以保持其可见性,这种情况很少发生,以至于任何人都不可能写出提案。

第二个例子

此示例 至少从函数返回一个值 - 因此我们必须查看允许移动而不是复制的异常的细节。

这里的规则是(N4659,§[class.copy.elision] / 3):

  

在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:

     
      
  • 如果return语句(9.6.3)中的表达式是一个(可能带括号的)id-expression,它指定一个对象,该对象具有在最内层封闭函数或lambda-的body或parameter-declaration-clause中声明的自动存储持续时间。表达,
  •   
     

[...]

     首先执行

重载决策以选择复制的构造函数,就好像该对象是由rvalue指定的一样。如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值。

return语句中的表达式(y)是一个id-expression,它指定在最内层封闭函数体中声明的具有自动存储持续时间的对象,因此编译器必须执行两阶段重载解析

但是,它目前正在寻找的是一个从X创建Y的构造函数。 X定义了一个(也是唯一一个)此类构造函数 - 但该构造函数按值接收其Y 。因为那是唯一可用的构造函数,那就是在重载决策中“获胜”的构造函数。由于它按值进行参数化,因此我们首先尝试将y视为右值的重载决策实际上没有任何区别,因为X没有正确类型的ctor收到它。

现在, if 我们定义了X这样的东西:

struct X
{
    X(Y &&y);
    X(Y const &y); 

    Y y_;
}

... 然后两阶段重载决策会产生实际效果 - 即使y指定左值,第一轮重载决策将其视为右值,因此,X(Y &&y)将被选中并用于创建返回的临时X - 也就是说,我们将获得移动而不是副本(即使y是左值,我们有一个带左值引用的复制构造函数。

答案 1 :(得分:-3)

为什么不使用render作为一切?或者如果使用函数传递outer。然后,在函数内部使用&outer