📜 ⬆️ ⬇️

Return by value and const variables in C ++ 11

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()); // OK f2(g()); // compile error f3(g()); // OK const int a = 0; f1(a); // OK f2(a); // compile error f3(a); // OK } 

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); // OK int a2 = 0; f2(a2); // OK const int ca1 = 0; f1(ca1); // compile error const int ca2 = 0; f2(ca2); // OK } 

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!

Source: https://habr.com/ru/post/183454/


All Articles