在c ++ 11中,是否在函数中返回std :: string移动或复制它?

时间:2018-01-09 01:26:45

标签: c++ c++11

假设我有以下代码。

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

int main() {
    std::string result = foo();
}

当我调用'foo'时,mystr中的数据是否被复制或移入result?我相信它是移动C ++ 11风格,但我希望澄清和/或链接显示。

谢谢!

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

5 个答案:

答案 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 并避免创建临时文件来复制和销毁它。这仍然是允许的,您的编译器可能会也可能不会在此处应用它。