Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I make my local variables const or movable?

My default behaviour for any objects in local scopes is to make it const. E.g.:

auto const cake = bake_cake(arguments);

I try to have as little non-functional code as I can as this increases readability (and offers some optimisation opportunities for the compiler). So it is logical to also reflect this in the type system.

However, with move semantics, this creates the problem: what if my cake is hard or impossible to copy and I want to pass it out after I'm done with it? E.g.:

if (tastes_fine(cake)) {
  return serve_dish(cake);
}

As I understand copy elision rules it's not guaranteed that the cake copy will be elided (but I'm not sure on this).

So, I'd have to move cake out:

return serve_dish(std::move(cake)); // this will not work as intended

But that std::move will do nothing useful, as it (correctly) will not cast Cake const& to Cake&&. Even though the lifetime of the object is very near its end. We cannot steal resources from something we promised not to change. But this will weaken const-correctness.

So, how can I have my cake and eat it too?

(i.e. how can I have const-correctness and also benefit from move semantics.)

like image 687
bitmask Avatar asked May 24 '20 14:05

bitmask


People also ask

Does const improve performance?

const correctness can't improve performance because const_cast and mutable are in the language, and allow code to conformingly break the rules. This gets even worse in C++11, where your const data may e.g. be a pointer to a std::atomic , meaning the compiler has to respect changes made by other threads.

Should I use const everywhere?

Yes, you should use const whenever possible. It makes a contract that your code will not change something. Remember, a non-const variable can be passed in to a function that accepts a const parameter. You can always add const, but not take it away (not without a const cast which is a really bad idea).

When should I use const in C++?

The const keyword allows you to specify whether or not a variable is modifiable. You can use const to prevent modifications to variables and const pointers and const references prevent changing the data pointed to (or referenced).

Is const a local variable?

If you declare a local variable const , you simply mark it immutable. It should never ever change its value. If you still try to modify it later on, you'll get a compilation error.


3 Answers

I believe it's not possible to move from a const object, at least with a standard move constructor and non-mutable members. However, it is possible to have a const automatic local object and apply copy elision (namely NRVO) for it. In your case, you can rewrite your original function as follows:

Cake helper(arguments)
{
   const auto cake = bake_cake(arguments);
   ...  // original code with const cake
   return cake;  // NRVO 
}

Then, in your original function, you can just call:

return serve_dish(helper(arguments));

Since the object returned by helper is already a non-const rvalue, it may be moved-from (which may be, again, elided, if applicable).

Here is a live-demo that demonstrates this approach. Note that there are no copy/move constructors called in the generated assembly.

like image 89
Daniel Langr Avatar answered Oct 16 '22 08:10

Daniel Langr


You should indeed continue to make your variables const as that is good practice (called const-correctness) and it also helps when reasoning about code - even while creating it. A const object cannot be moved from - this is a good thing - if you move from an object you are almost always modifying it to a large degree or at least that is implied (since basically a move implies stealing the resources owned by the original object) !

From the core guidelines:

You can’t have a race condition on a constant. It is easier to reason about a program when many of the objects cannot change their values. Interfaces that promises “no change” of objects passed as arguments greatly increase readability.

and in particular this guideline :

Con.4: Use const to define objects with values that do not change after construction


Moving on to the next, main part of the question:

Is there a solution that does not exploit NRVO?

If by NRVO you take to include guaranteed copy elision, then not really, or yes and no at the same. This is somewhat complicated. Trying to move the return value out of a return by value function doesn't necessarily do what you think or want it to. Also, a "no copy" is always better than a move performance-wise. Therefore, instead you should try to let the compiler do it's magic and rely in particular on guaranteed copy elision (since you use c++17). If you have what I would call a complex scenario where elision is not possible: you can then use a move combined with guaranteed copy elision/NRVO, so as to avoid a full copy.

So the answer to that question is something like: if you object is already declared as const, then you can almost always rely on copy-elision/return by value directly, so use that. Otherwise you have some other scenario and then use discretion as to the best approach - in rare cases a move could be in order(meaning it's combined with copy-elision).

Example of 'complex' scenario:

std::string f() {
  std::string res("res");
  return res.insert(0, "more: ");//'complex scenario': a reference gets returned here will usually mean a copy is invoked here.
}

Superior way to 'fix' is to use copy-elision i.e.:

return res;//just return res as we already had that thus avoiding copy altogether - it's possible that we can't use this solution for more *hairy/complex* scenarios.

Inferior way to 'fix' in this example would be;

return std::move(res.insert(0, "more: "));
like image 3
darune Avatar answered Oct 16 '22 08:10

darune


Make them movable if you can.

It's time to change your "default behaviour" as it's anachronistic.

If move semantics were built into the language from inception then making automatic variables const would have quickly become established as poor programming practice.

const was never intended to be used for micro-optimisations. Micro-optimisations are best left to the compiler. const exists primarily for member variables and member functions. It's also helped clean up the language a little: e.g. "foo" is a const char[4] type whereas in C it's a char[4] type with the curious understanding that you're not allowed to modify the contents.

Now (since C++11) const for automatic variables can actually be harmful as you observe, the time has come to stop this practice. The same can be said for const parameter by-value types. Your code would be less verbose too.

Personally I prefer immutable objects to const objects.

like image 3
Bathsheba Avatar answered Oct 16 '22 09:10

Bathsheba