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

Your example fall on the so-called Named Return Value Optimization, which is defined in this paragraph of the C++11 standard. So the compiler may elide the copy constructor (or move constructor since 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. The returned object would be moved if it were naming a function parameter, [class.copy]/32 (bold is mine):

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. [...]

In C++14, this last rule has changed. It also includes the case of automatic variables [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. [...]

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.


Like user4581301 said, I suspect copy elision to happen (not a move) in most implementations. For c++11 and c++14, the standard allows copy elision to happen but doesn't mandate it. In c++17, some instances of copy elision will become mandated. So, for c++11 and c++14, technically the answer depends on the implementation being used. In your case specifically, we're talking about a specific type of copy elision: return value optimization (RVO). To check whether RVO happens in your environment for your given case, you can run this code:

#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.


Since 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. 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.

So, yes, this code will move the string rather than copy it.

The expression in a return statement will not always be copied, however. If you 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. (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. Newer versions of the Standard make copy elision mandatory in most situations where it’s possible, but compilers were doing it even before that.

Tags:

C++

C++11