5

(kind of inspired by this answer although not related)

I've always been told (and been telling) that keeping const-correctness, even for short lived variables is valuable and good practice, e.g:

const std::string a = "Hello world";

Instead of

std::string a = "Hello world";

This:

  • Expresses intent more clearly.
  • Makes sure the variable is immutable so passing it to some function that might change it will make the compiler yell at you.
  • Might improve performance because of compiler optimizations.

Although ever since modern C++ introduced copy elision there have been a few clauses in the standard that allow the compiler to call the move constructor instead of the copy constructor:

In the following copy-initialization contexts, a move operation might be used instead of a copy operation:

(3.1) If the expression in a return statement ([stmt.return]) 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, or

(3.2) if the operand of a throw-expression ([expr.throw]) is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one),

Does this mean there can actually be a performance penalty for using const objects with a non-default copy/move constructor instead of an increase when faced with situations this kind of elision would apply to?

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • @Acorn When optimizers weren't that mature yet I've definitely seen more optimized assembly being generated by compilers than without `const`. (I think this was pre C++11 times) This might not be the case nowadays with optimizers being so good at finding out if the data is even modified at all though, so maybe it doesn't anymore and it's just an ancient relic. (like using `inline` for performance). – Hatted Rooster Mar 12 '19 at 17:18
  • Not sure what you mean, and of course I can't prove it, but it sounds like either a codegen bug (i.e. optimizing the `const` when they shouldn't have), a non-standard implementation (same) or a missed optimization (i.e. not optimizing both cases equally). – Acorn Mar 12 '19 at 17:21
  • @Acorn If you take the address of a non-const object and pass the non-const pointer to a function defined in another translation unit, the optimiser must assume that the object may have been modified and accessing the object later must be through memory, right? If the object is const, then passing a non-const pointer to it into another TU doesn't prevent the optimiser from caching the value of the variable in a register, since it cannot change. Is my reasoning wrong? – eerorika Mar 12 '19 at 17:29
  • 1
    @Acorn Here is a codegen difference: https://godbolt.org/z/xMuMVB – Max Langhof Mar 12 '19 at 17:47
  • By the way, there is a contrived case where `const` could actually improve performance: an `extern const` simple-enough object (like an `int`) with initializer, because even without internal linkage the compiler may leverage the fact the standard says the value is not allowed to change. – Acorn Mar 12 '19 at 17:47
  • @MaxLanghof Yeah, that is a much better example for the same reasoning as my previous comment. Well, I retract the "in any way" then :) Unsure if I should leave the comment, since I cannot edit it anymore. Still, note that those kinds of variables behave like `constexpr` ones, not "just `const`", so one could still say the only benefit of `const` is in cases where `constexpr` could be applied instead (and should be). – Acorn Mar 12 '19 at 17:52
  • @Acorn I think it basically boils down to "const references/pointers in function arguments are irrelevant, as the compiler can't be sure about their truthfulness from either side, but it _can_ be sure that `const` variables declared in a given scope cannot change." – Max Langhof Mar 12 '19 at 18:01
  • @eerorika Not sure what you mean. If the object is `const`, it may still have `mutable` members, for instance. Also, watch out for non-originally-`const` objects, because those offer no guarantees either. Even if it has no `mutable` members and it is originally `const`, doing a quick test looks like optimizers do not assume it hasn't changed. Even if they did, it is in general a bad idea to rely on `const` for performance. Assume you get no benefits at all, and if you know the object does not change, design the code to exploit that in another way. – Acorn Mar 12 '19 at 18:07
  • @Acorn Shouldn't [this](https://godbolt.org/z/Wo-5x2) allow (at least theoretically) for `test2()` to be optimized down to `foo(bar()); return true;`? I can understand if they don't have optimizer passes for this (because it's not usually useful), but there is no way `test2()` should ever be able to return `false`, no? Let me ask that as a question actually... – Max Langhof Mar 12 '19 at 18:08
  • @Acorn `it may still have mutable members` The optimisation that I suggest cannot be used for mutable members. But not all members are mutable. In fact mutable members hardly ever used. Sure, if all your members are mutable, then I agree that using const instances of that class would indeed be pointless. `doing a quick test looks like optimizers do not assume it hasn't changed` [Max's example](https://stackoverflow.com/users/9528746/max-langhof) demonstrates how an optimiser does exactly that (instead of non-const pointer, there is const reference; turns out that distinction is not important). – eerorika Mar 12 '19 at 18:20
  • @MaxLanghof Yes, it is my understanding that it should. However, it looks like compilers stop if they cannot use a constant expression -- so those optimizations only seem to work if you could have written `constexpr` to begin with, which in this case you can't due to `bar()`. – Acorn Mar 12 '19 at 18:21
  • @Acorn Well, at least in [this](https://godbolt.org/z/bf6CrP) case they can do it without `constexpr`... – Max Langhof Mar 12 '19 at 18:24
  • @eerorika I think you are confusing what I said. If a `const` object you pass has a single `mutable` member or a member with itself `mutable` members, then it has to be assumed they may have changed, and this in turn may imply other methods behave differently. As for Max's example, that is different: his case was a trivial one where the variables are effectively treated as `constexpr`. If you try to do the same with a very simple object of class type, then the optimization does not happen either (I can only get clang to do it if it is POD and I access the variable directly, no getter). – Acorn Mar 12 '19 at 18:32
  • @MaxLanghof But there you are not passing the address anywhere, so it is trivial to prove no one modifies it; that is a different optimization, and it would happen even without `const`. – Acorn Mar 12 '19 at 18:33
  • @Acorn True, I guess escape analysis is real scared... https://godbolt.org/z/Lgvo8O. Asked a new question here: https://stackoverflow.com/questions/55128512/does-const-allow-for-theoretical-optimization-here – Max Langhof Mar 12 '19 at 18:38

1 Answers1

3

Does this mean there can actually be a performance penalty for using const objects with a non-default copy/move constructor

Potentially yes. When returning a local object, constness would prevent the use of a move construction that would have been allowed by the quoted rules. The performance penalty might not exist in practice though, because NRVO may still elide the entire copy/move whether the variable is const or not.

NRVO isn't guaranteed though, and not necessarily always possible - or simply not enabled (debug builds), so it may be worth it to use non-const when returning local variables by value.

eerorika
  • 232,697
  • 12
  • 197
  • 326