Skip to content

Commit

Permalink
docs(rvo): 📝 correct description about rvo
Browse files Browse the repository at this point in the history
  • Loading branch information
Timothy-Liuxf committed May 22, 2024
1 parent 53a8b95 commit 24219f1
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 12 deletions.
12 changes: 6 additions & 6 deletions blogs/zh-CN/c_cpp/return-value-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ int main() {
}
```

因为,虽然编译器执行了返回值优化,不会真正地调用复制构造函数,但是按照标准还是要有复制构造函数的调用。而我们的复制构造函数的参数定义为了(左值)引用(需要注意这通常情况下不是一个好的设计),其只能引用[左值](./lvalue-and-rvalue.md),但 `Foo()``GetFoo()` 的返回值都不是左值,因此按照标准要求,没有匹配的复制构造函数可用,因此还是会报错。
因为,虽然编译器执行了返回值优化,不会真正地调用复制构造函数,但从语言标准的角度讲,调用复制构造函数也是符合标准的,因此为了保证语义一致性,还是需要匹配合适的复制构造函数的。而我们的复制构造函数的参数定义为了(左值)引用(需要注意这通常情况下不是一个好的设计),其只能引用[左值](./lvalue-and-rvalue.md),但 `Foo()``GetFoo()` 的返回值都不是左值,因此按照标准要求,没有匹配的复制构造函数可用,因此还是会报错。

返回值优化是一项比较古老的技术,[二十世纪的 C++ 编译器就已经支持返回值优化了](https://www.youtube.com/watch?v=3Ud9HryMUqA)

Expand All @@ -142,7 +142,7 @@ int main() {
}
```
这次,如果没有任何的优化,按照 C++98/11 的语言标准上要求,我们首先需要调用默认构造函数来构造 `GetFoo` 函数内的 `foo`,再使用其复制构造返回值,最后用返回值复制构造 `main` 函数中的 `foo`。
这次,如果没有任何的优化,我们首先需要调用默认构造函数来构造 `GetFoo` 函数内的 `foo`,再使用其复制构造返回值,最后用返回值复制构造 `main` 函数中的 `foo`。
如果仅考虑上面的返回值优化,我们并不能把两次复制构造均优化掉,因为 `return` 语句中 `return` 后的表达式并不是一个临时对象,那么返回值优化最多只能给我们优化掉一次复制构造。
Expand All @@ -152,16 +152,16 @@ int main() {
## C++11 标准
C++11 标准引入了[右值引用和移动语义](./rvalue-references-and-move-semantics.md)。因此对于之前所述的返回值优化的情况,如果该类定义了移动构造函数的话,那么标准要求的将不会是两次复制构造,而是两次移动构造。
C++11 标准引入了[右值引用和移动语义](./rvalue-references-and-move-semantics.md)。因此对于之前所述的返回值优化一节中的代码片段,如果该类定义了移动构造函数,那么在不进行返回值优化的情况下将不会是两次复制构造,而是两次移动构造。
但是,对于前面所述的具名返回值优化,由于 `return` 后面的表达式是左值,如果按照原本的规定,应当还是会进行一次复制构造的。但是,C++11 在此处做了一个特别的规定——在 `return` 后面的表达式(如果可能的话)[将会被自动当作右值表达式](https://zh.cppreference.com/w/cpp/language/return#.E8.87.AA.E5.8A.A8.E4.BB.8E.E5.B1.80.E9.83.A8.E5.8F.98.E9.87.8F.E5.92.8C.E5.BD.A2.E5.8F.82.E7.A7.BB.E5.8A.A8),从而调用移动构造函数,而不去调用复制构造函数。
但是,对于前面所述的具名返回值优化一节中的代码片段,由于 `return` 后面的表达式是左值,如果不进行具名返回值优化,应当还是会进行一次复制构造的。但是,C++11 在此处做了一个特别的规定——在 `return` 后面的表达式(如果可能的话)[将会被自动当作右值表达式](https://zh.cppreference.com/w/cpp/language/return#.E8.87.AA.E5.8A.A8.E4.BB.8E.E5.B1.80.E9.83.A8.E5.8F.98.E9.87.8F.E5.92.8C.E5.BD.A2.E5.8F.82.E7.A7.BB.E5.8A.A8),从而调用移动构造函数,而不去调用复制构造函数。
## 复制/移动消除
值得一提的是,C++98/03 标准中便允许实现进行[**复制消除**](https://zh.cppreference.com/w/cpp/language/copy_elision)(C++11 中增加了移动消除),即在一定条件下可以省略对象的复制构造(或移动构造)。RVO 和 NRVO 便是复制/移动消除的一种情况。但复制/移动消除在 C++14 以前并不是强制要求的。
值得一提的是,从语言标准的角度看,标准仍然需要保证无论实现进行什么优化,优化的结果都应当是标准所允许的。因此,为了让 RVO 和 NRVO 的结果符合标准,C++98/03 标准中便允许实现进行[**复制消除**](https://zh.cppreference.com/w/cpp/language/copy_elision)(C++11 中增加了移动消除),即在一定条件下可以省略对象的复制构造(或移动构造)。RVO 和 NRVO 便是复制/移动消除的一种情况。但复制/移动消除在 C++14 以前并不是强制要求的。
## C++17 标准
可以看到,在不执行复制/移动消除的情况下,C++11 已经要求是两次移动构造了。但是,遍观主流的编译器实现,返回值优化几乎已经成了标配,几乎所有的编译器都会做返回值优化。因此,标准的要求就与主流编译器的实现大相径庭。此外由于[其他的一些原因](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html),C++17 标准决定对复制/移动消除中的部分情况做出强制性要求。因此,C++17 引入了[临时量实质化](https://zh.cppreference.com/w/cpp/language/implicit_conversion#.E4.B8.B4.E6.97.B6.E9.87.8F.E5.AE.9E.E8.B4.A8.E5.8C.96)的概念,重新定义了临时对象的创建条件,让众多情况下不进行临时对象的创建。这样做的作用效果之一便是返回值优化在标准中得到了保证,编译器被强制要求进行返回值优化。这样,在 C++17 标准中,之前所述的情形中标准不再要求调用复制构造函数或移动构造函数了(因此之前所说的由于标准要求而无法通过编译的代码在 C++17 标准中也可以通过编译了)。不过,具名返回值优化还不是标准强制要求的。
可以看到,在不执行复制/移动消除的情况下,C++11 已经要求是两次移动构造了。但是,遍观主流的编译器实现,返回值优化几乎已经成了标配,几乎所有的编译器都会做返回值优化。因此,标准将其作为非强制性的特性就已经毫无意义了。此外,由于[其他的一些原因](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html),C++17 标准决定对复制/移动消除中的部分情况做出强制性要求。因此,C++17 引入了[临时量实质化](https://zh.cppreference.com/w/cpp/language/implicit_conversion#.E4.B8.B4.E6.97.B6.E9.87.8F.E5.AE.9E.E8.B4.A8.E5.8C.96)的概念,重新定义了临时对象的创建条件,让众多情况下不进行临时对象的创建。这样做的作用效果之一便是返回值优化在标准中得到了保证,编译器被强制要求进行返回值优化。这样,在 C++17 标准中,之前所述的情形中标准不再要求调用复制构造函数或移动构造函数了(因此之前所说的由于标准要求而无法通过编译的代码在 C++17 标准中也可以通过编译了)。不过,具名返回值优化还不是标准强制要求的。
12 changes: 6 additions & 6 deletions blogs/zh-TW/c_cpp/return-value-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ int main() {
}
```

因為,雖然編譯器執行了傳回值最佳化,不會真正地呼叫複製建構函式,但是按照標準還是要呼叫複製建構函式。而我們的複製建構函式的參數定義為一個(lvalue)參考(需要注意這通常情況下不是一個好的設計),其只能參考 [lvalue](./lvalue-and-rvalue.md),但 `Foo()``GetFoo()` 的傳回值都不是 lvalue,因此按照標準要求,沒有相符的複製建構函式可用,因此還是會報錯。
因為,雖然編譯器執行了傳回值最佳化,不會真正地呼叫複製建構函式,但從語言標準的角度講,呼叫複製建構函式也是符合標準的,因此為了保證語意一致性,仍然需要可用的複製建構函式。而我們的複製建構函式的參數定義為一個(lvalue)參考(需要注意這通常情況下不是一個好的設計),其只能參考 [lvalue](./lvalue-and-rvalue.md),但 `Foo()``GetFoo()` 的傳回值都不是 lvalue,因此按照標準要求,沒有相符的複製建構函式可用,因此還是會報錯。

傳回值最佳化是一項非常古老的技術,[二十世紀的 C++ 編譯器就已經支援傳回值最佳化了](https://www.youtube.com/watch?v=3Ud9HryMUqA)

Expand All @@ -142,7 +142,7 @@ int main() {
}
```
這次,如果沒有任何的最佳化,按照 C++98/11 的語言標準上要求,我們首先需要呼叫預設建構函式來建構 `GetFoo` 函式内的 `foo`,再使用其複製建構傳回值,最後用傳回值複製建構 `main` 中的 `foo`。
這次,如果沒有任何的最佳化,我們首先需要呼叫預設建構函式來建構 `GetFoo` 函式内的 `foo`,再使用其複製建構傳回值,最後用傳回值複製建構 `main` 中的 `foo`。
如果僅考慮上面的傳回值最佳化,我們并不能把兩次複製建構均省略掉,因為 `return` 陳述式中 `return` 后的運算式并不是一個暫存物件,那麽傳回值最佳化最多只能給我們省略掉一次複製建構。
Expand All @@ -152,16 +152,16 @@ int main() {
## C++11 標準
C++11 標準引入了 [rvalue 參考和移動語意](./rvalue-references-and-move-semantics.md)。因此對於之前所述的傳回值最佳化的情況,如果該類別定義了移動建構函式,那麽標準要求的將不會是兩次複製建構,而是兩次移動建構。
C++11 標準引入了 [rvalue 參考和移動語意](./rvalue-references-and-move-semantics.md)。因此對於之前所述的傳回值最佳化一節中的程式碼片段,如果該類別定義了移動建構函式,那麽在不進行傳回值最佳化的情況下將不會是兩次複製建構,而是兩次移動建構。
但是,對於前面所述的具名傳回值最佳化,由於 `return` 後面的運算式是 lvalue,如果按照原本的規定,應當還是會進行一次複製建構的。但是,C++11 在此處做了一個特別的規定——在 `return` 後面的表達式(如果可能的話)[將會自動作為 rvalue 運算式](https://zh.cppreference.com/w/cpp/language/return#.E8.87.AA.E5.8A.A8.E4.BB.8E.E5.B1.80.E9.83.A8.E5.8F.98.E9.87.8F.E5.92.8C.E5.BD.A2.E5.8F.82.E7.A7.BB.E5.8A.A8),從而呼叫移動建構函式,而不會呼叫複製建構函式。
但是,對於前面所述的具名傳回值最佳化一節中的程式碼片段,由於 `return` 後面的運算式是 lvalue,如果不進行具名傳回值最佳化,應當還是會進行一次複製建構的。但是,C++11 在此處做了一個特別的規定——在 `return` 後面的表達式(如果可能的話)[將會自動作為 rvalue 運算式](https://zh.cppreference.com/w/cpp/language/return#.E8.87.AA.E5.8A.A8.E4.BB.8E.E5.B1.80.E9.83.A8.E5.8F.98.E9.87.8F.E5.92.8C.E5.BD.A2.E5.8F.82.E7.A7.BB.E5.8A.A8),從而呼叫移動建構函式,而不會呼叫複製建構函式。
## 複製/移動 elision
值得一提的是,C++98/03 標準中便允許實現進行[**複製 elision**](https://zh.cppreference.com/w/cpp/language/copy_elision)(C++11 中增加了移動 elision),即在一定條件下可以省略物件的複製建構(或移動建構)。RVO 和 NRVO 便是複製/移動 elision 的一種情況。但複製/移動 elision 在 C++14 以前并不是强制要求的。
值得一提的是,從語言標準的角度看,標準仍然需要保證即使實現對程式進行最佳化,其最佳化的結果都應當是標準所允許的。因此,為了讓 RVO 和 NRVO 的結果符合標準,C++98/03 標準中便允許實現進行[**複製 elision**](https://zh.cppreference.com/w/cpp/language/copy_elision)(C++11 中增加了移動 elision),即在一定條件下可以省略物件的複製建構(或移動建構)。RVO 和 NRVO 便是複製/移動 elision 的一種情況。但複製/移動 elision 在 C++14 以前并不是强制要求的。
## C++17 標準
可以看到,在不執行複製/移動 elision 的情況下,C++11 已經要求是兩次移動建構了。但是,遍觀主要的編譯器的實現,傳回值最佳化幾乎已經成了標配,幾乎所有的編譯器都會做傳回值最佳化。因此,標准的要求與主流編譯器的實現大相徑庭。此外由於[其他的一些原因](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html),C++17 引入了[暫存數具體化](https://zh.cppreference.com/w/cpp/language/implicit_conversion#.E4.B8.B4.E6.97.B6.E9.87.8F.E5.AE.9E.E8.B4.A8.E5.8C.96)的概念,重定義了暫存物件的創建條件,讓眾多情況下不創建暫存物件。這樣做的作用效果之一便是傳回值最佳化在標準中得到了保證,編譯器被强制要求進行傳回值最佳化。這樣,在 C++17 標準中,之前所述的情形中標準不再要求呼叫複製建構函式或移動建構函式了(因此之前所説的由於標準要求而無法通過編譯的程式碼在 C++17 標準中也可以通過編譯了)。不過,具名傳回值最佳化還不是標準强制要求的。
可以看到,在不執行複製/移動 elision 的情況下,C++11 已經要求是兩次移動建構了。但是,遍觀主要的編譯器的實現,傳回值最佳化幾乎已經成了標配,幾乎所有的編譯器都會做傳回值最佳化。因此,標準將其作為非强制性的特性就已經毫無意義了。此外,由於[其他的一些原因](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html),C++17 引入了[暫存數具體化](https://zh.cppreference.com/w/cpp/language/implicit_conversion#.E4.B8.B4.E6.97.B6.E9.87.8F.E5.AE.9E.E8.B4.A8.E5.8C.96)的概念,重定義了暫存物件的創建條件,讓眾多情況下不創建暫存物件。這樣做的作用效果之一便是傳回值最佳化在標準中得到了保證,編譯器被强制要求進行傳回值最佳化。這樣,在 C++17 標準中,之前所述的情形中標準不再要求呼叫複製建構函式或移動建構函式了(因此之前所説的由於標準要求而無法通過編譯的程式碼在 C++17 標準中也可以通過編譯了)。不過,具名傳回值最佳化還不是標準强制要求的。

0 comments on commit 24219f1

Please sign in to comment.