范围末尾的左值是否可以被视为右值?

Can an lvalue at end of scope be treated as an rvalue?

本文关键字:是否 范围      更新时间:2023-10-16

编辑:考虑以下 2 个示例:

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)以强制编译器执行移动。在第二个示例中,我可以做什么来向编译器指示y可以移动?return std::move(y)

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

第一个示例

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

struct foo {
foo(foo const &f) { std::cout << "copy ctorn"; }
foo(foo &&f) { std::cout << "move ctorn"; }
};
foo outer;
{ 
foo inner;
// ...
outer = inner;
}

。正常运行的编译器必须生成打印出"复制 CTOR"而不是"移动 CTOR"的代码。实际上没有具体的引用 - 相反,有引用谈论函数返回值的异常,这在这里不适用,因为我们不处理函数的返回值。

至于为什么没有人处理这个问题:我猜这只是因为没有人打扰。从函数返回值的频率足够高,因此值得投入相当多的精力来优化它。创建一个非功能块,并在块中创建一个值,然后继续复制到块外部的值以保持其可见性很少发生,以至于似乎不太可能有人写出提案。

第二个例子

这个例子至少从一个函数返回一个值——所以我们必须看看允许移动而不是复制的异常的细节。

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

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

  • 如果 return 语句 (9.6.3( 中的表达式是一个(可能加括号的(id-表达式,该表达式命名一个对象,该对象具有在最内层封闭函数或 lambda 表达式的主体或参数声明子句中声明的自动存储持续时间,

[...]

首先执行为副本选择构造函数的重载解析,就像对象由右值指定一样。如果第一个重载解析失败或未执行,或者所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能符合 cv(,则会再次执行重载解析,并将对象视为左值。

return 语句中的表达式 (y( 是一个 id 表达式,它命名一个对象,并在最里面的封闭函数的主体中声明了自动存储持续时间,因此编译器必须执行两阶段重载解析。

但是,它此时要寻找的是从Y创建X的构造函数。X定义了一个(并且只有一个(这样的构造函数,但该构造函数通过值接收Y。由于这是唯一可用的构造函数,因此它是在重载分辨率中"获胜"的构造函数。由于它按值获取参数,因此我们首先尝试将y视为右值的重载解决方案这一事实实际上没有任何区别,因为X没有正确类型的 ctor 来接收它。

现在,如果我们定义X这样的东西:

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

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

为什么不把outer用于所有事情呢?或者,如果使用函数传递&outer.然后,在函数内部使用*inner.

相关文章: