In many programming languages, it is possible to declare objects and variables to be constant. And, accordingly, there are recommendations to do so, if you are not going to change their values. With the advent of the new standard, C ++ has a recommendation to return objects from functions by value, because even without an
RVO , program performance can be improved by using move semantics. What happens if you use these two recommendations together: return a constant object by value? Let's try to figure out further.
Looking through the video from the recently held conference C ++ Now I came across one interesting point (pitfall). At the end of one of the speeches, the speaker gives the following code:
struct s { ... }; sf() { const sr; ...; return r; }
and asks what happens to the
r object when returning from a function?
Suppose that the structure
s has both a copy constructor and a motion constructor, and the function
f contains code that prevents the use of RVO. What happens to the
r object when it returns from a function? If the object is created without the
const specifier, then when it is returned from the function it will be moved, and if created with
const it will be
copied . The similar behavior is noticed in GCC and in Clang (in the Visual Studio did not check). What is it: a bug or an expected behavior? If an object is immediately destroyed, why not move it?
Since C ++ 03, many of us have become accustomed to the fact that temporary objects and constants are almost the same thing. And this is logical, because the behavior of those and others was similar: they allowed to call only functions that take arguments by value or a constant link (or simply assign their values to these types):
void f1(int a) { } void f2(int& a) { } void f3(const int& a) { } int g() { return 0; } int main() { f1(g());
In C ++ 11, the situation has changed: now we can change temporary objects using rvalue links. But not constant. According to the standard, any change to objects declared with the
const specifier (whether using const_cast or perversions with pointers) leads to undefined behavior. It turns out that if the compiler generated the code using the displacement constructor, it would lead the program into a state of undefined behavior, which is unacceptable. In other words, const-correctness is at the compiler in a higher priority than such optimization. Still, C ++ is a strongly typed language and any implicit coercion of references and pointers from
const to normal type is prohibited, for example:
const A a -> A && .
')
Consider the following code:
void f1(int& a) { } void f2(const int& a) { } int main() { int a1 = 0; f1(a1);
Standard C ++ does not allow implicitly removing qualifiers from objects. If some type
A can be reduced to both
A & and
const A & , then
const A can only be reduced to
const A & . Now let's replace the lvalue reference to the rvalue reference by adding calls to
std :: move when passing parameters to functions ... Exactly! We cannot cast
const A a to
A && , but we can
cast const A && . If you write such a constructor, the compiler uses it to return constant variables from functions, but there is no more sense in this than from the regular copy constructor. Therefore, this type of link is usually not used.
Conclusion
As it turns out, some recommended practices are not combined. In C ++, you constantly have to watch here and there, no matter how hard you fall over another reef. Now you need to keep track of the constants, so that they do not complicate your life. After all, no one is immune from such a code (and even if you rely on the RVO and think that this case is not important to you, you can spend a lot of time looking for the reasons why this code is not compiled if there is no copy constructor in the structure
s ):
struct s { ... }; s foo(); s bar() { const auto r = foo(); if (r.check_something()) throw std::exception(); return r; }
And even in this code there is a copying of an object (another pitfall):
s bar() { auto&& r = foo(); ...; return r; }
Prefer this option:
s bar() { auto r = foo(); ...; return r; }
Or, only if you value each displacement constructor:
s bar() { auto&& r = foo(); ...; return std::move(r); }
Note
Do not confuse the previous example with the following code that returns an rvalue link:
s&& bar() { auto&& r = foo(); ...; return std::move(r); }
Never do that; here the link to the object already destroyed is returned.
And may the Force be with you!