4

In another question a user made a comment that returning a const std::string loses move construction efficiency and is slower.

Is it really true that assigning a string of return of this method:

const std::string toJson(const std::string &someText);

const std::string jsonString = toJson(someText);

... is really slower than the non-const version:

std::string toJson(const std::string &str);

std::string jsonString = toJson(someText);

And what is the meaning of move-construction efficiency in this context?

I've never heard of that limitation before and do not remember having seen that in the profiler. But I'm curious.

Edit: There is a suggested question asking: What is move semantics?. While some of the explanations of course relate to efficiency, it explains what move semantics means, but does not address why returning a const value can have negative side effects regarding performance.

benjist
  • 2,740
  • 3
  • 31
  • 58
  • Do you question that it loses move semantics, or do you question that it's slower? Or both? – Barry Jun 12 '19 at 21:17
  • 1
    Take a look a the generated code to see. Or run each a couple of million times with random strings and measure. – Some programmer dude Jun 12 '19 at 21:18
  • @Barry does it lose move semantics? – Oblivion Jun 12 '19 at 21:20
  • 5
    Also, returning a `const` value or not doesn't really matter. It's what the caller assigns the result to that matters. – Some programmer dude Jun 12 '19 at 21:20
  • @Someprogrammerdude That was my thought as well. But i'm still curious if there is something to it which might apply indirectly. – benjist Jun 12 '19 at 21:22
  • 2
    There is no point in returning a const value. Just return by value. – NathanOliver Jun 12 '19 at 21:22
  • Does it compile slower or run slower? I would assume that disabling move, assigment and copy because of the const will take some compile time but as @someprogrammerdude pointed out, there is no point in returning a const value. – fonZ Jun 12 '19 at 21:31
  • 1
    Returning a const *value* (as opposed to a reference) **never** makes sense. Don't do it. Your compiler should have warned you, even. – Nikos C. Jun 12 '19 at 22:18
  • Possible duplicate of [What is move semantics?](https://stackoverflow.com/questions/3106110/what-is-move-semantics) – L. F. Jun 16 '19 at 01:12

3 Answers3

13

Consider the following functions:

std::string f();
std::string const g();

There is no difference between:

std::string s1 = f();
std::string s2 = g();

We have guaranteed copy elision now, in both of these cases we're constructing directly into the resulting object. No copy, no move.

However, there is a big difference between:

std::string s3, s4;
s3 = f(); // this is move assignment
s4 = g(); // this is copy assignment

g() may be an rvalue, but it's a const rvalue. It cannot bind to the string&& argument that the move assignment operator takes, so we fall back to the copy assignment operator whose string const& parameter can happily accept an rvalue.

Copying is definitely slower than moving for types like string, where moving is constant time and copying is linear and may require allocation.

Don't return const values.


On top of that, for non-class types:

int f();
int const g();

These two are actually the same, both return int. It's an odd quirk of the language that you cannot return a const prvalue of non-class type but you can return a const prvalue of class type. Easier to just pretend you can't do the latter either, since you shouldn't.

Barry
  • 286,269
  • 29
  • 621
  • 977
1

Without reading the specification or anything else, if we just think about it logically...

For example, lets say you have

// Declare the function
std::string const my_function();

// Initialize a non-constant variable using the function
std::string my_string = my_function();

The value returned by the function could be copied to a temporary object, the value from inside the function is then destructed. The temporary object (which is constant) is then copied to the my_string object, and then the temporary object is destructed. Two copies and two destructions. Sounds a little excessive, don't you think? Especially considering that both the value inside the function and the temporary object will be destructed, so they don't really need to keep their contents.

Wouldn't it be better if the copying could be elided, perhaps both of them? Then what could happen is that the value from inside the function is moved directly into the my_string object. The const status of anything doesn't matter, since the objects being moved from will be destructed next anyway.

The latter is what modern compiler do, they move even if the function is declared to return a const value. And even if the value or object inside the function is const as well.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
0

Statements like this have certain meaning in terms of initialization,

std::string getString();
const std::string getConstantString();

std::string str = getString(); // 1
const std::string str = getConstantString(); //2

Both initialization statements 1 and 2 come under copy initialization. Now it depends on cv-qualification (const and volatile) of return type, there are two possibilities, if return type is cv-unqualified and move constructor available for class then object will be move initialized as in statement 1, and if return type is cv-qualified then object will be copy initialized as in statement 2.
But there is an optimization called copy-elision(ignores cv-qualification) and due to copy-elision, The objects are constructed directly into the storage where they would otherwise be copied/moved to.

There are two type of copy-elision, NRVO, "named return value optimization" and RVO, "return value optimization", but from c++17 Return value optimization is mandatory and no longer considered as copy elision.
Please see following link copy-elision for more details.

Vikas Awadhiya
  • 290
  • 1
  • 8