C++右值引用和完美转发
C++右值引用和完美轉(zhuǎn)發(fā)
- 何為引用
- 引用必須是左值
- 右值引用
- 完美轉(zhuǎn)發(fā)
- move()
- 使用move的優(yōu)點(diǎn)
- move 左值測試
- move 右值測試
- 注意
- 參考鏈接
看到有些同學(xué),調(diào)用函數(shù)的時候總喜歡使用std::move希望避免一些開銷,而實際上由于他并不理解什么是右值引用、完美轉(zhuǎn)發(fā),導(dǎo)致這種努力成為了徒勞,反增笑柄。
本文為記錄我學(xué)習(xí)右值引用和完美轉(zhuǎn)發(fā)的筆記。
何為引用
C++新增了一種復(fù)合類型,也就是引用變量。通過引用,就可以使用該引用名稱或變量名稱來指向變量。
引用必須是左值
對于對象的引用必須是左值(常量引用除外)
const引用能夠綁定到臨時對象, 并將臨時對象的生命周期由”創(chuàng)建臨時對象的完整表達(dá)式”提升至”綁定到的const引用超出作用域”。 non-const 引用沒有這個功能
右值引用
右值引用可以從字面意思上理解,指的是以引用傳遞(而非值傳遞)的方式使用 C++ 右值。用 “&&” 表示。
完美轉(zhuǎn)發(fā)
- 使用forward()再模板可以做到完美轉(zhuǎn)發(fā),減少拷貝
- 完美轉(zhuǎn)發(fā)的好處是函數(shù)可以動態(tài)的接受函數(shù)參數(shù),從而免去了有可能的拷貝
- 完美轉(zhuǎn)發(fā)的函數(shù)內(nèi)部,需要使用forward()來把接受的參數(shù)轉(zhuǎn)化為合適的形式傳遞出去
我們看Chromium提供的例子:
#include <cstdio> #include <utility>class MyType {public:MyType() {}MyType(MyType&& other) { fprintf(stderr, "move ctor\n"); }MyType(const MyType& other) { fprintf(stderr, "copy ctor\n"); }; };void Store(const MyType& type) {fprintf(stderr, "store (copy)\n"); }void Store(MyType&& type) {fprintf(stderr, "store (move)\n"); }template<typename T> void ProcessAndStore(T&& var) {// Process// ...// The type of |var| could be an rvalue reference, which means we should pass// an rvalue to Store. However, it could also be an lvalue reference, which// means we should pass an lvalue.// Note that just doing Store(var); will always pass an lvalue and doing// Store(std::move(var)) will always pass an rvalue. Forward does the right// thing by casting to rvalue only if var is an rvalue reference.Store(std::forward<T>(var)); }int main(int argc, char **argv) {MyType type;// In ProcessAndStore: T = MyType&, var = MyType&ProcessAndStore(type);// In ProcessAndStore: T = MyType, var = MyType&&ProcessAndStore(MyType()); }move()
-
這個函數(shù)從C++11開始也變?yōu)镾TL函數(shù)了
-
移動賦值函數(shù)
-
std::move函數(shù)可以以非常簡單的方式將左值引用轉(zhuǎn)換為右值引用
-
應(yīng)用之一是unique_ptr
-
移動之后括號內(nèi)的值就不要再用了, move之后本身就會析構(gòu), functions that receive rvalues may act destructively on your variable, so using the variable’s contents afterward may result in undefined behaviour. The only valid things you may do after calling std::move() on a variable are:
- Destroy it
- Assign to it (ie. replace its contents)
- 上一點(diǎn)的原因是,rvalue reference的語義是函數(shù)可以認(rèn)為rvalue外面沒有再被引用了。所以可以淺拷貝,可以析構(gòu),如果你還要用里面的值的話,就會有問題。
- doing std::move() on an lvalue reference is bad!!! Code producing an lvalue reference expects the object to remain valid. But code receiving an rvalue reference expects to be able to steal from it. This leaves you with a reference pointing to a potentially-invalid object.
- 一個例外是當(dāng)函數(shù)參數(shù)類型是到模板形參的右值引用(“轉(zhuǎn)發(fā)引用”或“通用引用”)時,該情況下轉(zhuǎn)而使用 std::forward
使用move的優(yōu)點(diǎn)
move用于移動構(gòu)造時,可以使移動構(gòu)造函數(shù)成為淺拷貝,由于rvalue出去也沒有引用了,所以很安全,性能也比深拷貝要好
#include <cstdio> #include <cstring> #include <vector>class MyType {public:MyType() {pointer_ = new int;*pointer_ = 1;memset(array_, 0, sizeof(array_));vector_.push_back(3.14);}MyType(MyType&& other) {fprintf(stderr, "move ctor\n");// Steal the memory, null out |other|.// 因為other會被析構(gòu),所以要把它賦值為null,之所以敢這么做,因為傳進(jìn)來的是個右值,外面沒有人用了。pointer_ = other.pointer_;other.pointer_ = nullptr;// Copy the contents of the array.memcpy(array_, other.array_, sizeof(array_));// Swap with our (empty) vector.vector_.swap(other.vector_);}~MyType() {delete pointer_;}private:int* pointer_;char array_[42];std::vector<float> vector_; };void ProcessMyType(MyType type) { }MyType MakeMyType(int a) {if (a % 2) {MyType type;return type;}MyType type;return type; }int main(int argc, char **argv) {// MakeMyType returns an rvalue MyType.// Both lines below call our move constructor.MyType type = MakeMyType(2);ProcessMyType(MakeMyType(2)); }move 左值測試
struct testc {int a; } void test_func(testc a) {}; void test_func_1(testc &&a) {}; int main() {testc lv;testc &a = lv; //左值引用,沒有構(gòu)造testc &&b = static_cast<testc&&>(lv); // 右值引用,沒有構(gòu)造testc c = std::move(lv); // 把一個右值引用賦值給一個左值,如果有移動構(gòu)造函數(shù),則調(diào)用移動構(gòu)造函數(shù)。沒有則調(diào)用拷貝構(gòu)造函數(shù)test_func(std::move(lv)); // 這個move僅僅是把調(diào)用test_func時候創(chuàng)建形參的過程從拷貝構(gòu)造變成了移動構(gòu)造。并沒有減多少開銷test_func_1(std::move(lv)); // 沒有構(gòu)造函數(shù),test_func_1直接對形參進(jìn)行右值引用testc c1;c1 = std::move(lv); // 這兩句話更慘,首先構(gòu)造了c1,調(diào)用了構(gòu)造函數(shù),然后調(diào)用了operator=(testc &c)賦值c1 = lv; // 同上,是否使用move并沒有帶來額外的開銷。testc &d = std::move(lv);testc &&e std::move(lv); // 右值引用幅值給右值引用,沒有構(gòu)造, lv仍然可以對結(jié)構(gòu)體進(jìn)行操作testc f = lv; // 拷貝構(gòu)造函數(shù),注意這里沒有調(diào)用operator+ }move 右值測試
struct testc {int a;testc (testc &&other) {a = c.a;}// 移動構(gòu)造還有一種寫法testc(testc&& other) {// Note that although the type of |other| is an rvalue reference,// |other| itself is an lvalue, since it is a named object. In order// to ensure that the move assignment is used, we have to explicitly// specify std::move(other).*this = std::move(other);} } testc rv() { return testc(); } testc rv(int a) {// This is here to circumvent some compiler optimizations,// to ensure that we will actually call a move constructor.// 這點(diǎn)比較精髓,如果沒有這個wrapper函數(shù),可能所有的左值右值的構(gòu)造都會被編譯器優(yōu)化掉if (a % 2) {testc c;return c;}testc c;return c; }void receiver(testc &&a) {... }int main() {testc lv; // 構(gòu)造函數(shù)lv = rv(2); // rv里面會調(diào)用構(gòu)造函數(shù),構(gòu)造一個臨時右值,然后移動構(gòu)造到一個臨時變量(第一個臨時變量析構(gòu)),然后這個臨時變量再調(diào)用operator=復(fù)制,然后rv的臨時右值析構(gòu)(第二個臨時變量析構(gòu))testc a = rv(); // rv里面調(diào)用構(gòu)造函數(shù),然后a就會直接用這個對象,這中間沒有任何構(gòu)造析構(gòu)拷貝了,原因是因為編譯器優(yōu)化掉了。testc a1 = rv(2); // rv里面調(diào)用構(gòu)造函數(shù),然后移動構(gòu)造!!!(如果沒有定義移動構(gòu)造函數(shù),那么就用拷貝構(gòu)造函數(shù))。然后rv(2)出來的臨時右值被析構(gòu)testc &&b = rv(2); // 同上。rv里面調(diào)用構(gòu)造函數(shù),然后移動構(gòu)造到一個新的地方,rc里面的析構(gòu),b指向的是新拷貝的一塊地方 (由此可見,使用一個右值引用之后,實際上是另外開辟了一個空間存儲,這時這個變量就變成左值了!)testc &&c = static_cast<testc&&>(rv(1)); // 同上 testc &&d = static_cast<testc>(rv(1)); // 同上 testc &&d1 = std::move(lv); // 同上,mv創(chuàng)建一個區(qū)域,然后lv移動構(gòu)造過去,持有的是那片空間testc &&e = std::move(rv(2)); // 同上,但是感覺move那個地方還會有一層析構(gòu),并且程序結(jié)束之后就不會析構(gòu)了,不要這么做,rv(2)已經(jīng)是右值了receiver(rv()); // receiver的形參成為rv()里的臨時變量,然后形參就退化成右值了!!!receiver(move(lv)); // 這樣就沒有任何構(gòu)造析構(gòu)了 }注意
參考鏈接
總結(jié)
以上是生活随笔為你收集整理的C++右值引用和完美转发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++使用StringPiece减少st
- 下一篇: io_uring设计理念及使用方式总结