简体   繁体   English

在 c++11 中,函数中返回的 std::string 是移动还是复制?

[英]In c++11, does returning a std::string in a function move or copy it?

suppose I have the following code.假设我有以下代码。

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

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

When I call 'foo', is the data in mystr copied or moved into result ?当我调用 'foo' 时, mystr的数据是mystr复制还是移动到result I believe that it is moved C++11 style, but I was hoping for clarification and/or links to show that.我相信它是移动的 C++11 风格,但我希望澄清和/或链接来表明这一点。

Thanks!谢谢!

Edit: I suppose I want to know the answer to this question when compiling with g++ for c++11 or later.编辑:我想我想在使用 g++ for c++11 或更高版本编译时知道这个问题的答案。

Your example fall on the so-called Named Return Value Optimization, which is defined in this paragraph of the C++11 standard .您的示例属于所谓的命名返回值优化,它在 C++11 标准的本段中定义。 So the compiler may elide the copy constructor (or move constructor since C++14).因此编译器可能会省略复制构造函数(或自 C++14 起移动构造函数)。 This elision is not mandatory.这种省略不是强制性的。

In C++11 , if the compiler does not perform this elision, the returned string will be copy constructed .在 C++11 中,如果编译器不执行此省略,则返回的字符串将被复制构造 The returned object would be moved if it were naming a function parameter, [class.copy]/32 (bold is mine):如果返回的对象正在命名函数参数, [class.copy]/32 (粗体是我的),它将被移动:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter , and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue .当满足或将满足复制操作的省略标准时,源对象是函数参数,并且要复制的对象由左值指定,重载决议选择复制的构造函数是首先执行,好像对象是由 rvalue 指定的 [...] [...]

In C++14, this last rule has changed.在 C++14 中,最后一条规则发生了变化。 It also includes the case of automatic variables [class.copy]/32 :它还包括自动变量[class.copy]/32 的情况

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue .当满足复制/移动操作的省略条件,但不满足异常声明,并且要复制的对象由左值指定时,或者当 return 语句中的表达式是(可能带括号的)id-在最内层封闭函数或 lambda 表达式的主体或参数声明子句声明具有自动存储持续时间的对象的表达式,首先执行重载决议以选择副本的构造函数,就好像该对象是由右值指定的一样. [...] [...]

So in your example code, and in C++14 , if the compiler does not elide the copy/move construction, the returned string will be move constructed .因此,在您的示例代码和C++14 中,如果编译器没有省略复制/移动构造,则返回的字符串将被移动构造

Like user4581301 said, I suspect copy elision to happen (not a move) in most implementations.就像 user4581301 所说的,我怀疑在大多数实现中会发生复制省略(不是移动)。 For c++11 and c++14, the standard allows copy elision to happen but doesn't mandate it.对于 c++11 和 c++14,标准允许发生复制省略,但没有强制要求。 In c++17, some instances of copy elision will become mandated.在 c++17 中,一些复制省略的实例将成为强制性的。 So, for c++11 and c++14, technically the answer depends on the implementation being used.因此,对于 c++11 和 c++14,从技术上讲,答案取决于所使用的实现。 In your case specifically, we're talking about a specific type of copy elision: return value optimization (RVO).就您的情况而言,我们正在讨论一种特定类型的复制省略:返回值优化 (RVO)。 To check whether RVO happens in your environment for your given case, you can run this code:要检查您的环境中是否发生了给定案例的 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();
}

My implementation opts for RVO - no move takes place.我的实现选择了 RVO - 没有发生任何变化。

Since std::string result = foo();由于std::string result = foo(); is an initializer, it will call the constructor rather than the assignment operator.是初始化器,它将调用构造函数而不是赋值运算符。 In C++11 or newer, there is guaranteed to be a move constructor with prototype std::basic_string::basic_string( basic_string&& other ) noexcept .在 C++11 或更新版本中,保证有一个带有原型std::basic_string::basic_string( basic_string&& other ) noexcept的移动构造std::basic_string::basic_string( basic_string&& other ) noexcept On every actually-existing implementation, this moves the contents rather than copying them.在每个实际存在的实现中,这会移动内容而不是复制它们。 Although I don't believe the standard mandates a particular implementation, it does say this particular operation must run in constant and not linear time, which precludes a deep copy.虽然我不相信该标准要求特定的实现,但它确实说这个特定的操作必须在恒定而不是线性的时间内运行,这排除了深度复制。 As the return value of foo() is a temporary rvalue, that is the constructor that will be called in this snippet.由于foo()的返回值是一个临时的右值,这就是将在此代码段中调用的构造函数。

So, yes, this code will move the string rather than copy it.所以,是的,这段代码将移动字符串而不是复制它。

The expression in a return statement will not always be copied, however.然而,并不总是复制return语句中的表达式。 If you return std::string("SOMELONGVALUE");如果你return std::string("SOMELONGVALUE"); (a programmatic constructor ), the implementation is permitted to construct the result in place instead. 程序化构造函数),则允许实现就地构造结果。 If foo() returns a std::string& and returns anything other than a temporary, that will be returned by reference.如果foo()返回std::string&并返回临时值以外的任何内容,则将通过引用返回。 (Returning a reference to a temporary that's been destroyed is, as you know, undefined behavior!) And some compilers, even before C++11, would perform copy elision and avoid creating a temporary only to copy and destroy it. (如您所知,返回对已被销毁的临时文件的引用是未定义的行为!)有些编译器,甚至在 C++11 之前,会执行复制省略并避免创建临时文件仅用于复制和销毁它。 Newer versions of the Standard make copy elision mandatory in most situations where it's possible, but compilers were doing it even before that.较新版本的标准在大多数可能的情况下强制执行复制省略,但编译器甚至在此之前就已经这样做了。

The way the most compilers implement return of a class type is to pass an extra "hidden" argument to the function that is a pointer to the memory where the value being returned should be constructed.大多数编译器实现类类型返回的方式是将额外的“隐藏”参数传递给函数,该参数是指向应该构造返回值的内存的指针。 So the called function can copy or move the return value into that memory as needed without regard to the call site.因此,被调用的函数可以根据需要将返回值复制或移动到该内存中,而无需考虑调用位置。

With your example code, such a compiler could even use that same memory to hold the mystr variable, constructing it directly there, and never using either the move or copy constructor of std::string.使用您的示例代码,这样的编译器甚至可以使用相同的内存来保存mystr变量,直接在那里构造它,并且从不使用 std::string 的移动或复制构造函数。

In my VS2015, the compiler does invoke move ctor when returning an temp variable in such a trivial case.在我的 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, whether the compiler trigger RVO depends on the price of copying, you can see the mechanism of RVO in below: https://www.ibm.com/developerworks/community/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/entry/RVO_V_S_std_move?lang=en亲,编译器是否触发RVO取决于复制的价格,RVO的机制见下: https : //www.ibm.com/developerworks/community/blogs/5894415f-be62-4bc0-81c5-3956e82276f3/条目/RVO_V_S_std_move?lang=en

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM