6

编辑:考虑以下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而不是X在 (2) 中的实例以避免 (N)RVO。

4

2 回答 2

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;
}

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

至于为什么没有人处理这个问题:我猜这只是因为没有人打扰。从函数返回值经常发生,值得投入大量精力来优化它。创建一个非功能块,并在块中创建一个值,然后将其复制到块外的一个值以保持其可见性,这种情况很少发生,以至于似乎不太可能有人编写提案。

第二个例子

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

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

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

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

[...]

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

return 语句中的表达式 ( y) 是一个 id 表达式,它使用在最内层封闭函数的主体中声明的自动存储持续时间来命名对象,因此编译器必须执行两阶段重载决策。

但是,此时它正在寻找的是一个构造函数来X从 a创建一个Y. X定义了一个(也是唯一一个)这样的构造函数——但该构造函数Y 通过值接收它。由于这是唯一可用的构造函数,因此它是在重载决议中“获胜”的构造函数。由于它按值接受参数,因此我们首先尝试将重载解析y视为右值这一事实并没有真正产生任何影响,因为X没有正确类型的 ctor 来接收它。

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

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

    Y y_;
}

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

于 2017-07-19T01:56:33.510 回答
-3

为什么不只是outer用于一切?或者,如果使用函数传入&outer. 然后,在函数内部使用*inner.

于 2017-07-19T01:49:00.897 回答