Skip to content

Commit ffa890f

Browse files
authored
Merge pull request #515 from abseil/axgillies-patch-1
Update 2018-04-20-totw-120.md
2 parents 916ff6f + 1e5eb2d commit ffa890f

1 file changed

Lines changed: 40 additions & 42 deletions

File tree

_posts/2018-04-20-totw-120.md

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@ order: "120"
1010

1111
Originally 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

3736
What 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

4543
Return 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

5047
This tip only applies when you are returning a non-reference local variable.
5148
Returning any other expression will not trigger this problem.
5249

5350
## The Problem
5451

5552
There 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
5754
implicit 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
8178
logging 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

10199
If you are only reading the value, you could also make sure to disable the
102100
optimizations 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++
129127
std::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
139137
bytes. This function can leave garbage bytes on the string if NRVO does not
140138
happen.
141139

@@ -149,8 +147,8 @@ the block is finished. Like this:
149147
std::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

Comments
 (0)