为什么可以';这些变量不能移动吗? 《C++移动语义》中似乎有很多错失的机会。我想了解这些背后的基本原理,以及为什么在以下情况下,该标准在定义变量何时移动方面没有更积极的态度: string f() { string s; return s + " "; }

为什么可以';这些变量不能移动吗? 《C++移动语义》中似乎有很多错失的机会。我想了解这些背后的基本原理,以及为什么在以下情况下,该标准在定义变量何时移动方面没有更积极的态度: string f() { string s; return s + " "; },c++,c++11,move-semantics,C++,C++11,Move Semantics,这将调用操作符+(常量字符串和常量字符*),而不是操作符+(字符串和常量字符*),我相信这是因为s是一个左值。难道标准不能说,在函数中最后一次使用局部变量时,该变量被认为是可移动的吗 我想有一个类似的例子: struct A { A(string&&); }; string g() { string s; return s; // s is moved } A h() { string s; return s; // s can't be move

这将调用
操作符+(常量字符串和常量字符*)
,而不是
操作符+(字符串和常量字符*)
,我相信这是因为
s
是一个左值。难道标准不能说,在函数中最后一次使用局部变量时,该变量被认为是可移动的吗

我想有一个类似的例子:

struct A { A(string&&); };
string g()
{
    string s;
    return s; // s is moved
}
A h()
{
    string s;
    return s; // s can't be moved!
}

g
使用移动语义将数据从
s
移动到返回值,但是
h
不会编译,因为
s
不会在
h
中移动。我认为这是因为标准对
g
有一个特例,它本质上说,如果您返回与返回类型完全相同的局部变量,则该变量将被移动。为什么规则不是如果你返回一个局部变量,它会被移动,不管它的类型是什么?

我肯定它可能需要移动,但有人会提出另一种情况,他们认为“很明显”的是,
s
是最后一次被使用,因此应该被移动

最终,您将拥有定义编译器需要执行的数据流分析的标准。作者决定保守地划定界限,允许实现在这方面变得愚蠢。程序员总是可以编写
std::move
将副本更改为move


另一种可能性是,如果代码不再使用对象,标准会说对象是移动还是复制是未指定的。这将使实现尽可能聪明。我很确定这是个坏主意:实际上,用户通常不关心他们的对象是否被移动,但他们有时需要解决这个问题。

简单地说,在当前标准中,这是一个不必要的限制,这使得自动移动取决于拷贝省略的可用性

另一个例子是:

struct X{
  std::string s;
};

std::string foo(){
  X x;
  return x.s; // no automatic move
};
我打开了一个关于未来标准提案论坛的帖子,可以看到。根据Richard Smith的建议,我直接给Mike Miller发了一封邮件,就这一问题发表了一篇核心文章,得到了如下回复:

[…]根据Richard上面的总结,这听起来确实是一个合理的问题,因此我将在问题列表的下一次修订中为其打开一个问题。谢谢

因此,对于C++14,所有这些限制都可能消失,每次返回局部变量时,都会自动移动

Richard总结如下:

更具体地说,[class.copy]p31有一条规则,即对于语句“return id expression”,可以省略该副本,其中id表达式命名一个局部变量,并且该变量与函数的返回类型具有相同的cv非限定类型。建议我们在任何时候使用语句“return id expression;”时执行自动移动,其中id表达式命名局部变量或函数参数,而不管变量的类型如何


如果我返回了s+“a”+s+“b”,应该移动哪个
s
?最好仅在非常明显的情况下自动将内容转换为右值引用,以避免看起来像副本的内容被意外破坏。您混合使用了
wstring
char*
,我将它们全部更改为
string
(类型与问题无关)。另外,您说但是h不编译,
h()
应该可以很好地编译,因为
A(string&)
构造函数不是
显式的
,因此将从
s
创建
A
对象。而且,移动并不总是从函数返回值的最佳选择。在大多数情况下,NRVO可能优于复制,在所有这些情况下明确要求移动会抑制NRVO。@DavidBrown:“都不移动”在您的示例中仍然允许将
s
移动到
s+“”
。但它开始显得随意。您确定在
g
中,字符串实际上是由返回移动的吗?@MarkB:C++11 12.8/32:“当满足或将满足省略复制操作的标准时,除非源对象是函数参数,并且要复制的对象由左值指定,否则将首先执行重载解析以选择复制的构造函数,就像对象由右值指定一样“。但是,移动符合省略条件,因此不能保证字符串被移动,它可以直接构造到返回值中。它保证不会被复制,因为
string
确实有一个move-ctor。这会涵盖问题中的两个例子,还是只包括第二个?@Steve:啊,我想只包括第二个。例如
使用本地变量返回expr\u很难评估,最好不要使用。用户可以自己在那里插入
std::move
,因为复制省略(又名(N)RVO)无论如何都不适用。@JonathanWakely:Yay!而且,它们仍然不会像我的代码片段中那样移动子对象:(是的,我认为对移动成员的关注是让对象处于一种会让其析构函数感到意外的状态,因此您仍然需要显式的
std::move
,在那里说“我知道我在做什么”@JonathanWakely:这似乎有点奇怪。如果除了包含类本身之外,任何人都可以访问该成员,那么在进入析构函数时,您没有任何保证。此外,“subobject”将包括数组元素,这与dtor没有问题。我想如果在表达式中使用s两次,我们可能会有未定义的行为(move将放在编译器使用的最后一个上,但哪一个是未定义的,类似于(++i)+i如何具有未定义的值)