假设我有以下代码。
std::string foo() {
std::string mystr("SOMELONGVALUE");
return mystr;
}
int main() {
std::string result = foo();
}
当我调用'foo'时,mystr
中的数据是否被复制或移入result
?我相信它是移动C ++ 11风格,但我希望澄清和/或链接显示。
谢谢!
编辑:我想在使用g ++编译c ++ 11或更高版本时,我想知道这个问题的答案。
答案 0 :(得分:5)
您的示例属于所谓的命名返回值优化,该优化在this paragraph of the C++11 standard中定义。因此,编译器可能会忽略复制构造函数(或移动构造函数,因为C ++ 14)。这个省略不是强制性的。
在C ++ 11 中,如果编译器没有执行此省略,则返回的字符串将是 copy construct 。如果返回的对象是命名函数参数[class.copy]/32(粗体是我的),则会移动它:
当满足或将满足复制操作的省略标准时,除了源对象是函数参数这一事实,并且要复制的对象由左值指定,重载决策选择的构造函数首先执行复制,就好像对象是由右值指定。 [...]
在C ++ 14中,最后一条规则发生了变化。它还包括自动变量[class.copy]/32:
的情况当满足复制/移动操作的省略条件时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式为(可能是括号内的)时)id-expression命名具有自动存储持续时间的对象在最内层封闭函数或lambda表达式的body 或parameter-declaration-clause 中声明,选择重载决策首先执行副本的构造函数,就像对象是由右值指定一样。 [...]
因此,在您的示例代码和C ++ 14 中的中,如果编译器没有删除复制/移动构造,则返回的字符串将是移动构造。
答案 1 :(得分:3)
就像user4581301所说的那样,我怀疑在大多数实现中都会发生复制省略(不是移动)。对于c ++ 11和c ++ 14,该标准允许复制省略发生,但并不强制要求。在c ++ 17中,复制省略的一些实例将成为强制要求。因此,对于c ++ 11和c ++ 14,从技术上讲,答案取决于所使用的实现。在具体情况下,我们讨论的是特定类型的复制省略:返回值优化(RVO)。要检查RVO是否在您的环境中针对您的特定情况发生,您可以运行以下代码:
#include <iostream>
struct Foo {
Foo() { std::cout << "Constructed" << std::endl; }
Foo(const Foo &) { std::cout << "Copy-constructed" << std::endl; }
Foo(Foo &&) { std::cout << "Move-constructed" << std::endl; }
~Foo() { std::cout << "Destructed" << std::endl; }
};
Foo foo() {
Foo mystr();
return mystr;
}
int main() {
Foo result = foo();
}
我的实施选择了RVO - 没有移动。
答案 2 :(得分:0)
大多数编译器实现类类型返回的方式是传递额外的&#34;隐藏&#34;函数的参数,该函数是指向应该构造返回值的内存的指针。因此,被调用的函数可以根据需要将返回值复制或移动到该内存中,而不考虑调用站点。
使用您的示例代码,这样的编译器甚至可以使用相同的内存来保存mystr
变量,直接在那里构造它,并且从不使用std :: string的move或copy构造函数。
答案 3 :(得分:0)
在我的VS2015中,编译器在这样一个简单的情况下返回临时变量时会调用move ctor。
class A {
public:
A(int _x) :x(_x) {}
A(const A& a) {
cout << "copy ctor." << endl;
x = a.x;
}
A(A&& a) {
cout << "move ctor." << endl;
x = 123;
}
private:
int x;
};
A foo() {
A temp = { 7 };
return temp; //invoke move ctor
}
int main() {
A a = foo();
return 0;
}
Besieds,编译器是否触发RVO取决于 复制价格,你可以在下面看到RVO的机制: https://www.ibm.com/developerworks/community/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/entry/RVO_V_S_std_move?lang=en
答案 4 :(得分:0)
由于std::string result = foo();
是初始值设定项,因此它将调用构造函数而不是赋值运算符。在C ++ 11或更高版本中,保证有一个带有原型std::basic_string::basic_string( basic_string&& other ) noexcept
的移动构造函数。在每个实际存在的实现中,这会移动内容而不是复制它们。虽然我不认为该标准要求特定的实现,但它确实说这个特定的操作必须在恒定而非线性的时间内运行,这排除了深层复制。由于foo()
的返回值是临时右值,因此将在此代码段中调用构造函数。
所以,是的,这段代码会移动字符串而不是复制它。
但是,return
语句中的表达式并不总是被复制。如果您return std::string("SOMELONGVALUE");
(程序化构造函数),则允许实现构建结果。如果foo()
返回std::string&
并返回临时以外的任何内容,则将通过引用返回。 (正如你所知,返回对已被销毁的临时文件的引用是未定义的行为!)并且一些编译器,即使在C11之前,也会执行 copy elision 并避免创建临时文件来复制和销毁它。这仍然是允许的,您的编译器可能会也可能不会在此处应用它。