<dl id="opymh"></dl>

<div id="opymh"></div>
      <div id="opymh"><tr id="opymh"></tr></div>

        <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

        <em id="opymh"></em>

        <em id="opymh"><ol id="opymh"></ol></em>

              頻道欄目
              首頁 > 程序開發 > 軟件開發 > C++ > 正文
              C++關于左值,右值,左值引用,右值引用,std::move, std::foward等知識講解
              2018-07-27 15:23:16         來源:We_are_family678的博客  
              收藏   我要投稿

              關于左值和右值的定義

              左值和右值在C中就存在,不過存在感不高,在C++尤其是C++11中這兩個概念比較重要,左值就是有名字的變量(對象),可以被賦值,可以在多條語句中使用,而右值呢,就是臨時變量(對象),沒有名字,只能在一條語句中出現,不能被賦值。

              在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用綁定一個右值,如 :

              const int& i = 3;

              在這種情況下,右值不能被修改的。但是實際上右值是可以被修改的,如 :

              T().set().get();

              T 是一個類,set 是一個函數為 T 中的一個變量賦值,get 用來取出這個變量的值。在這句中,T() 生成一個臨時對象,就是右值,set() 修改了變量的值,也就修改了這個右值。

              既然右值可以被修改,那么就可以實現右值引用。右值引用能夠方便地解決實際工程中的問題,實現非常有吸引力的解決方案。

              右值引用

              左值的聲明符號為”&”, 為了和左值區分,右值的聲明符號為”&&”。

              給出一個實例程序如下

              #include 
              
              void process_value(int& i) 
              { 
                std::cout << "LValue processed: " << i << std::endl; 
              } 
              
              void process_value(int&& i) 
              { 
                std::cout << "RValue processed: " << i << std::endl; 
              } 
              
              int main() 
              { 
                int a = 0; 
                process_value(a);
                process_value(1); 
              }
              
              

              結果如下

              [email protected]:~$ g++ -std=c++11  test.cpp
              [email protected]:~$ ./a.out 
              LValue processed: 0
              RValue processed: 1
              

              Process_value 函數被重載,分別接受左值和右值。由輸出結果可以看出,臨時對象是作為右值處理的。

              下面涉及到一個問題

              x的類型是右值引用,指向一個右值,但x本身是左值還是右值呢?C++11對此做出了區分:

              Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.

              對上面的程序稍作修改就可以印證這個說法

              #include 
              
              void process_value(int& i) 
              { 
                std::cout << "LValue processed: " << i << std::endl; 
              } 
              
              void process_value(int&& i) 
              { 
                std::cout << "RValue processed: "  << std::endl; 
              } 
              
              int main() 
              { 
                int a = 0; 
                process_value(a);
                int&& x = 3;
                process_value(x); 
              }
              
              [email protected]:~$ g++ -std=c++11  test.cpp
              [email protected]:~$ ./a.out 
              LValue processed: 0
              LValue processed: 3
              

              x 是一個右值引用,指向一個右值3,但是由于x是有名字的,所以x在這里被視為一個左值,所以在函數重載的時候選擇為第一個函數。

              右值引用的意義

              直觀意義:為臨時變量續命,也就是為右值續命,因為右值在表達式結束后就消亡了,如果想繼續使用右值,那就會動用昂貴的拷貝構造函數。(關于這部分,推薦一本書《深入理解C++11》)

              右值引用是用來支持轉移語義的。轉移語義可以將資源 ( 堆,系統對象等 ) 從一個對象轉移到另一個對象,這樣能夠減少不必要的臨時對象的創建、拷貝以及銷毀,能夠大幅度提高 C++ 應用程序的性能。臨時對象的維護 ( 創建和銷毀 ) 對性能有嚴重影響。

              轉移語義是和拷貝語義相對的,可以類比文件的剪切與拷貝,當我們將文件從一個目錄拷貝到另一個目錄時,速度比剪切慢很多。

              通過轉移語義,臨時對象中的資源能夠轉移其它的對象里。

              在現有的 C++ 機制中,我們可以定義拷貝構造函數和賦值函數。要實現轉移語義,需要定義轉移構造函數,還可以定義轉移賦值操作符。對于右值的拷貝和賦值會調用轉移構造函數和轉移賦值操作符。如果轉移構造函數和轉移拷貝操作符沒有定義,那么就遵循現有的機制,拷貝構造函數和賦值操作符會被調用。

              普通的函數和操作符也可以利用右值引用操作符實現轉移語義。

              轉移語義以及轉移構造函數和轉移復制運算符

              以一個簡單的 string 類為示例,實現拷貝構造函數和拷貝賦值操作符。

               class MyString { 
               private: 
                char* _data; 
                size_t_len; 
                void _init_data(const char *s) { 
               _data = new char[_len+1]; 
               memcpy(_data, s, _len); 
               _data[_len] = '\0'; 
                } 
               public: 
                MyString() { 
               _data = NULL; 
               _len = 0; 
                } 
              
                MyString(const char* p) { 
               _len = strlen (p); 
               _init_data(p); 
                } 
              
                MyString(const MyString& str) { 
               _len = str._len; 
               _init_data(str._data); 
               std::cout << "Copy Constructor is called! source: " << str._data << std::endl; 
                } 
              
                MyString& operator=(const MyString& str) { 
               if (this != &str) { 
              _len = str._len; 
              _init_data(str._data); 
               } 
               std::cout << "Copy Assignment is called! source: " << str._data << std::endl; 
               return *this; 
                } 
              
                virtual ~MyString() { 
               if (_data) free(_data); 
                } 
               }; 
              
               int main() { 
                MyString a; 
                a = MyString("Hello"); 
                std::vector vec; 
                vec.push_back(MyString("World")); 
               }
               Copy Assignment is called! source: Hello 
               Copy Constructor is called! source: World

              這個 string 類已經基本滿足我們演示的需要。在 main 函數中,實現了調用拷貝構造函數的操作和拷貝賦值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是臨時對象,也就是右值。雖然它們是臨時的,但程序仍然調用了拷貝構造和拷貝賦值,造成了沒有意義的資源申請和釋放的操作。如果能夠直接使用臨時對象已經申請的資源,既能節省資源,有能節省資源申請和釋放的時間。這正是定義轉移語義的目的。

              我們先定義轉移構造函數。

                MyString(MyString&& str) { 
               std::cout << "Move Constructor is called! source: " << str._data << std::endl; 
               _len = str._len; 
               _data = str._data; 
               str._len = 0; 
               str._data = NULL; 
               }

              有下面幾點需要對照代碼注意:
              1. 參數(右值)的符號必須是右值引用符號,即“&&”。
              2. 參數(右值)不可以是常量,因為我們需要修改右值。
              3. 參數(右值)的資源鏈接和標記必須修改。否則,右值的析構函數就會釋放資源。轉移到新對象的資源也就無效了。

              現在我們定義轉移賦值操作符。

                MyString& operator=(MyString&& str) { 
               std::cout << "Move Assignment is called! source: " << str._data << std::endl; 
               if (this != &str) { 
              _len = str._len; 
              _data = str._data; 
              str._len = 0; 
              str._data = NULL; 
               } 
               return *this; 
               }

              這里需要注意的問題和轉移構造函數是一樣的。

              增加了轉移構造函數和轉移復制操作符后,我們的程序運行結果為 :

              由此看出,編譯器區分了左值和右值,對右值調用了轉移構造函數和轉移賦值操作符。節省了資源,提高了程序運行的效率。

              有了右值引用和轉移語義,我們在設計和實現類時,對于需要動態申請大量資源的類,應該設計轉移構造函數和轉移賦值函數,以提高應用程序的效率。

              關于std::move()和std::forward 再次推薦一本書:《effective modern C++》

              英文版的,這里有篇關于其中item25的翻譯不錯

              請看這里

              但是這幾點總結的不錯

              std::move執行一個無條件的轉化到右值。它本身并不移動任何東西;

              std::forward把其參數轉換為右值,僅僅在那個參數被綁定到一個右值時;

              std::move和std::forward在運行時(runtime)都不做任何事。

              點擊復制鏈接 與好友分享!回本站首頁
              上一篇:關于C++中new、operator new和placement new的區別詳解
              下一篇:關于C++多態的基礎知識講解
              相關文章
              圖文推薦
              點擊排行

              關于我們 | 聯系我們 | 廣告服務 | 投資合作 | 版權申明 | 在線幫助 | 網站地圖 | 作品發布 | Vip技術培訓 | 舉報中心

              版權所有: 紅黑聯盟--致力于做實用的IT技術學習網站

              极速飞艇好假
              <dl id="opymh"></dl>

              <div id="opymh"></div>
                  <div id="opymh"><tr id="opymh"></tr></div>

                    <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

                    <em id="opymh"></em>

                    <em id="opymh"><ol id="opymh"></ol></em>

                          <dl id="opymh"></dl>

                          <div id="opymh"></div>
                              <div id="opymh"><tr id="opymh"></tr></div>

                                <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

                                <em id="opymh"></em>

                                <em id="opymh"><ol id="opymh"></ol></em>