In what cases is it (unsafe) to refer to a return value?

I often use the following notation:

const Datum& d = a.calc(); // continue to use d 

This works when the result of the calculation is on the stack, see http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ . Although the compilers are probably optimized here, it's nice to hide temporary objects.

Today, I realized that the contents of d became invalid after the data was written to member a . In this particular case, the get function simply returned a link to another element, which, however, was completely unrelated to the record. Like this:

 const Datum& d = a.get(); // ... some operation ... a.somedata = Datum2(); // now d is invalid. 

Again, somedata has nothing to do with d or get() here.

Now I ask myself:

  • What side effects can lead to invalidity?
  • Is it wrong to assign return values ​​to a constant reference? (Especially when I do not know the interior function)

My application is single-threaded, with the exception of the Qt GUI-Thread.

+4
source share
4 answers

What side effects can lead to invalidity?

Saving a reference to the internal state of the class (i.e., compared to extending the temporary lifetime of the class) and then calling any non-constant member functions can potentially lead to invalidating the link.

Is it wrong to use return value for const reference? (Especially when I do not know the interior function)

I would say that it is a bad practice to keep a reference to the internal state of an instance of a class, mutating the instance of the class and continuing to use the original link (unless it is documented that non-constant functions are not invalid reference)

+5
source

You seem to be afraid that the elite failed. This auto x = some_func(); will result in an extra move over auto&& x = some_func(); when some_func() returns a temporary object.

You should not be.

If elision fails, it means your compiler is incompetent or compiles with overtly hostile settings. And you cannot survive hostile settings or incompetent compilers: incompetent compilers can turn a+=b with integers into for (int i = 0; i < abs(b); ++i) {if (b>0) ++a; else --a;} for (int i = 0; i < abs(b); ++i) {if (b>0) ++a; else --a;} and violate more than one iota of the standard.

Elision is a feature of the main language. Do not write bad code just because you do not believe that this will happen.


You must commit the link if you want a link to the data provided by the function, and not an independent stable copy of the data. If you do not understand the service life of the return value of a function, capturing by reference is simply unsafe.

Even if you know that the data will be stable, more time is spent on maintaining the code than writing: the person reading your code should be able to immediately see that your assumption is true. And non-local errors are bad: it would seem that a harmless change to the function you are calling should not violate your code.


End result: take things at a cost if you have no good reason.

Taking things by value makes it easier for you and the compiler to explain your code. This increases locality.

When you have a good reason not to do this, then take things from the link.

Depending on the context, this good reason may not need to be very strong. But this should not be based on the assumption of an incompetent compiler.

Premature pessimization should be avoided, but this should be premature optimization. Taking (or saving) links instead of values ​​should be something you do when and if you find performance issues. Write clean, clear code. Drag complexity into densely written types, and the external interface will be clean and simple. Take things by value because the values ​​are deformed.


Optimization is interchangeable. By making more code simpler, you can simplify your work (and be more productive). Then, when you identify the details in which performance is important, you can spend some energy there to make the code faster.

A great example is foundational code: foundational code (which is used everywhere) quickly becomes a general resistance to performance if it is not written with regard to performance and ease of use. In this case, you want to hide the complexity inside the type and expose a simple easy-to-use external interface that does not require the user to understand the guts.

But some code in some random function? Use values, the easiest containers to use, with the friendliest O-notation for the most expensive operation you perform, and the simplest interface. Vectors, if reasonable (avoid premature pessimization), but don't sweat a few cards.

Find 1% -10% of your code that takes 90% -99% of the time and will do it quickly. If the rest of your code has good O-notation performance (so it won't get terribly slower with large data sets than you are testing), you'll be in good shape. Then start testing with funny data sets and find the slow parts.

+6
source

I'm not sure that I am responding exactly to what you expected to hear, but ... the const keyword has nothing to do with the "unsafe" here. Even if you return a non-const link, it may become invalid. const means that it is not allowed to change. For example, if your get() returns a const member or get() itself is defined as const like this const some_refetence_t& get() const { return m_class_member; } const some_refetence_t& get() const { return m_class_member; } . Now about your questions at the end:

  • What side effects can lead to invalidity?

Changing the initial value can have many side effects. For example, suppose the return value is a reference to an object on the heap that has been deleted ... or the return value is cached while the original value receives updates. These are all design issues. If such a case can occur by design, the return value should be by value (and in the case of caching, it should not be cached! :)).

  1. Is it wrong to use return value for const reference? (Especially when I do not know the interior function)

Same. If by design you do not need to change the object that you get (by reference or by value), then it is best to define it as const . Once you define something as const , the compiler will make sure that you are not trying to modify it in any way in the code.

0
source

It is rather a question of how to return your functions. You should also have a thorough understanding of the type of return value and its semantics.

-one
source

All Articles