C++ 在c++;在函数中返回std::string会移动还是复制它?

C++ 在c++;在函数中返回std::string会移动还是复制它?,c++,c++11,C++,C++11,假设我有以下代码 std::string foo() { std::string mystr("SOMELONGVALUE"); return mystr; } int main() { std::string result = foo(); } 当我调用“foo”时,mystr中的数据是否被复制或移动到result?我相信这是C++11风格,但我希望得到澄清和/或链接来说明这一点 谢谢 编辑:我想在使用g++for c++11或更高版本编译时,我想知道这个问题的答案

假设我有以下代码

std::string foo() {
    std::string mystr("SOMELONGVALUE");
    return mystr;
}

int main() {
    std::string result = foo();
}
当我调用“foo”时,
mystr
中的数据是否被复制或移动到
result
?我相信这是C++11风格,但我希望得到澄清和/或链接来说明这一点

谢谢

编辑:我想在使用g++for c++11或更高版本编译时,我想知道这个问题的答案。

大多数编译器实现类类型返回的方式是向函数传递一个额外的“隐藏”参数,该函数是指向要构造返回值的内存的指针。因此,被调用函数可以根据需要将返回值复制或移动到该内存中,而不考虑调用位置


在您的示例代码中,这样的编译器甚至可以使用相同的内存来保存
mystr
变量,直接在那里构造它,并且从不使用std::string的move或copy构造函数。

在我的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;
}
A类{
公众:
A(int_x):x(x){}
A(常数A&A){

cout因为
std::string result=foo();
是一个初始值设定项,它将调用构造函数而不是赋值运算符。在C++11或更高版本中,保证会有一个带有原型的移动构造函数。在每个实际存在的实现中,这会移动内容,而不是复制内容。虽然我不相信标准要求特定的实现,它确实说这个特定的操作必须在常数时间而不是线性时间内运行,这就排除了深度复制。因为
foo()
的返回值是一个临时的右值,这就是将在此代码段中调用的构造函数

因此,是的,这段代码将移动字符串而不是复制它


但是,
return
语句中的表达式不会总是被复制。如果
return std::string(“SOMELONGVALUE”);
(编程构造函数),则允许实现就地构造结果。如果
foo()
返回一个
std::string&
并返回除临时文件以外的任何文件,该文件将通过引用返回。(正如您所知,返回对已销毁临时文件的引用是未定义的行为!)有些编译器,甚至在C++11之前,会执行复制省略,并避免创建一个临时文件来复制和销毁它。标准的较新版本在大多数可能的情况下强制执行复制省略,但编译器甚至在这之前就已经这样做了。

就像user4581301所说的,我怀疑复制省略会发生(不是一个动作)在大多数实现中。对于c++11和c++14,标准允许发生拷贝省略,但不强制执行。在c++17中,某些拷贝省略实例将强制执行。因此,对于c++11和c++14,技术上的答案取决于所使用的实现。具体来说,我们讨论的是一种特定类型的拷贝省略:re转向值优化(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();
}
#包括
结构Foo{

Foo(){std::cout您的示例属于所谓的命名返回值优化,它在中定义。因此编译器可能省略复制构造函数(或从C++14开始移动构造函数)。此省略不是强制性的

在C++11中,如果编译器不执行此省略,则返回的字符串将被复制构造。如果返回的对象正在命名函数参数,则返回的对象将被移动(粗体是我的):

当满足或将满足省略复制操作的条件时,除非源对象是函数参数,并且要复制的对象由左值指定,否则将首先执行重载解析以选择的构造函数,就像对象由右值指定一样[…]

在C++14中,最后一条规则已更改。它还包括自动变量的情况:

当满足省略复制/移动操作的条件,但不满足异常声明的条件,并且要复制的对象由左值指定,或者当返回语句中的表达式为a(可能带括号)时命名具有自动存储持续时间的对象的id表达式,该对象在最里面的封闭函数或lambda表达式的body或parameter declaration子句中声明,重载解析选择的构造函数。首先执行复制,就好像对象由右值指定一样ong>[…]


因此,在您的示例代码中,以及在C++14中,如果编译器不省略复制/移动构造,则返回的字符串将被移动构造。

我希望使用复制省略,而不是移动,但这在C++17之前是无法保证的。有用的阅读:阅读链接时,请特别注意“即使对象的复制/移动构造函数和/或析构函数有副作用。”如果你不知道会有副作用,这可能会很糟糕。@user4581301:即使在C++17中也不能保证(它们只保证RVO复制省略,而不是NRVO;
mystr
有一个名称,所以NRVO规则适用)。也就是说,对于大多数编译器,我希望NRVO能应用于这样一个简单的情况,如果我发现它不适用,就切换编译器。@ShadowRanger我可以更正。我错过了那个重要的细节,也没有机会使用C++17并以艰难的方式克服错误。是的,对不起,我误读了你的第一个答案。我删除了原始注释。Remov不要担心。我误读了另一个人的答案,昨天不得不收回一条评论。谢谢你这么客气。规范的措辞不清楚;但是,我相信“首先执行副本就像对象被右值指定一样”的正确解释意味着第一次尝试w