@@ -10,15 +10,14 @@ order: "120"
1010
1111Originally posted as TotW #120 on August 16, 2012
1212
13- *by Samuel Benzaquen, [(sbenza@google.com)](mailto:sbenza@gmail .com)*
13+ *by Samuel Benzaquen, [(sbenza@google.com)](mailto:sbenza@google .com)*
1414
15- Let's suppose you have a code snippet like below, which relies on an RAII
16- cleanup function, which seems to be working as expected:
15+ Let's suppose you have this code snippet, which seems to be working as expected:
1716
1817```c++
19- MyStatus DoSomething() {
20- MyStatus status;
21- auto log_on_error = RunWhenOutOfScope ([&status] {
18+ absl::Status DoSomething() {
19+ absl::Status status;
20+ auto log_on_error = absl::MakeCleanup ([&status] {
2221 if (!status.ok()) LOG(ERROR) << status;
2322 });
2423 status = DoA();
@@ -31,45 +30,43 @@ MyStatus DoSomething() {
3130}
3231```
3332
34- A refactor changes the last line from `return status;` to
35- `return MyStatus ();` and suddenly the code stopped logging errors.
33+ A refactor changes the last line from `return status;` to `return
34+ absl::OkStatus ();` and suddenly the code stopped logging the errors.
3635
3736What is going on?
3837
3938## Summary
4039
41- Never access (read or write) the return variable of a function after the return
42- statement has run. Unless you take great care to do it correctly, the behavior
43- is unspecified.
40+ Never access (read or write) the return variable after the return statement has
41+ run. Unless you take great care to do it correctly, the behavior is unspecified.
4442
4543Return variables are implicitly accessed by destructors after they have been
46- copied or move (See Section 6.6.3 of the C++11 standard [stmt.return]), which
47- is how this unexpected access occurs, but the copy/move may be elided, which
48- is why behavior is undefined.
44+ copied or moved ([stmt.return]), which is how this unexpected access occurs, but
45+ the copy/move may be elided, which is why behavior is unspecified.
4946
5047This tip only applies when you are returning a non-reference local variable.
5148Returning any other expression will not trigger this problem.
5249
5350## The Problem
5451
5552There are 2 different optimizations on return statements that can modify the
56- behavior of our original code snippet: NRVO (See [TotW #11](/tips/11) ) and
53+ behavior of that code snippet: the named return value optimization (NRVO ) and
5754implicit moves.
5855
59- The *before* code worked because copy elision is taking place and the
60- `return` statement is not actually doing any work. The variable `status` is
61- already constructed in the return address and the cleanup object is seeing this
62- unique instance of the `MyStatus ` object _after_ the return statement.
56+ The *before* code was working because NRVO is being performed and the `return`
57+ statement is not actually doing any work. The variable `status` is already
58+ constructed in the return address and the cleanup object is seeing this unique
59+ instance of the `Status ` object _after_ the return statement.
6360
64- In the *after* code, copy elision is not being applied and the returned variable
65- is being moved into the return value. `RunWhenOutOfScope()` runs after the
66- move operation is done and it is seeing a moved-from `MyStatus` object .
61+ In the *after* code, NRVO is not being performed and the returned variable is
62+ being moved into the return value. The cleanup object is being run after the
63+ move operation is done and it is seeing a moved-from `Status` .
6764
68- Note that the *before* code was not correct either, as it relies on the copy
69- elision optimization for correctness. We encourage you to rely on copy elision
70- for performance (See [TotW #24](/tips/24)), but not correctness. After all,
71- copy elision is an _optional_ optimization and compiler options or quality of
72- implementation of the compiler can affect whether or not it happens.
65+ Note that the *before* code was not correct either, as it relies on NRVO for
66+ correctness. We encourage you to rely on NRVO for performance (See TotW #24),
67+ but not correctness. After all, NRVO is an _optional_ optimization and compiler
68+ options or quality of implementation of the compiler can affect whether or not
69+ it happens.
7370
7471## Solution
7572
@@ -81,8 +78,8 @@ processing, and one that calls the first one and does the post-processing (ie
8178logging on error). Eg:
8279
8380```c++
84- MyStatus DoSomething() {
85- MyStatus status;
81+ absl::Status DoSomething() {
82+ absl::Status status;
8683 status = DoA();
8784 if (!status.ok()) return status;
8885 status = DoB();
@@ -91,26 +88,27 @@ MyStatus DoSomething() {
9188 if (!status.ok()) return status;
9289 return status;
9390}
94- MyStatus DoSomethingAndLog() {
95- MyStatus status = DoSomething();
91+
92+ absl::Status DoSomethingAndLog() {
93+ absl::Status status = DoSomething();
9694 if (!status.ok()) LOG(ERROR) << status;
9795 return status;
9896}
9997```
10098
10199If you are only reading the value, you could also make sure to disable the
102100optimizations instead. That would force a copy to be made all the time and the
103- post-processing will not see a moved-from value. E.g. :
101+ post-processing will not see a moved-from value. Eg :
104102
105103```c++
106- MyStatus DoSomething() {
107- MyStatus status_no_nrvo;
104+ absl::Status DoSomething() {
105+ absl::Status status_no_nrvo;
108106 // 'status' is a reference so NRVO and all associated optimizations
109107 // will be disabled.
110108 // The 'return status;' statements will always copy the object and Logger
111109 // will always see the correct value.
112- MyStatus & status = status_no_nrvo;
113- auto log_on_error = RunWhenOutOfScope ([&status] {
110+ absl::Status & status = status_no_nrvo;
111+ auto log_on_error = absl::MakeCleanup ([&status] {
114112 if (!status.ok()) LOG(ERROR) << status;
115113 });
116114 status = DoA();
@@ -123,19 +121,19 @@ MyStatus DoSomething() {
123121}
124122```
125123
126- ## Another example:
124+ ## Another Real Life Example
127125
128126```c++
129127std::string EncodeVarInt(int i) {
130128 std::string out;
131- StringOutputStream string_output(&out);
132- CodedOutputStream coded_output(&string_output);
129+ proto2::io:: StringOutputStream string_output(&out);
130+ proto2::io:: CodedOutputStream coded_output(&string_output);
133131 coded_output.WriteVarint32(i);
134132 return out;
135133}
136134```
137135
138- `CodedOutputStream` does some work in the destructor to trim unused trailing
136+ `CodedOutputStream` does some work on the destructor to trim unused trailing
139137bytes. This function can leave garbage bytes on the string if NRVO does not
140138happen.
141139
@@ -149,8 +147,8 @@ the block is finished. Like this:
149147std::string EncodeVarInt(int i) {
150148 std::string out;
151149 {
152- StringOutputStream string_output(&out);
153- CodedOutputStream coded_output(&string_output);
150+ proto2::io:: StringOutputStream string_output(&out);
151+ proto2::io:: CodedOutputStream coded_output(&string_output);
154152 coded_output.WriteVarint32(i);
155153 }
156154 // At this point the streams are destroyed and they already flushed.
0 commit comments