close

<article class="mx-1 my-1">
<h3>目錄</h3>
<ol>
    <li><a href="#%E7%89%A9%E4%BB%B6%E7%9A%84%E7%89%B9%E6%80%A7">物件的特性</a></li>
    <li><a href="#lvalue%E8%88%87prvalue">lvalue與prvalue</a></li>
    <li><a href="#lvalue%E8%88%87prvalue%E5%9C%A8%E5%AF%A6%E9%9A%9B%E7%A8%8B%E5%BC%8F%E7%9A%84%E9%81%8B%E4%BD%9C">lvalue與prvalue在實際程式的運作</a></li>
    <li><a href="#xvalue%E8%88%87move-semantic">xvalue與move semantic</a></li>
    <li><a href="#%E7%B8%BD%E7%B5%90">總結</a></li>
</ol>

<!--more-->

<h3>正文</h3>
<h4 id="物件的特性">物件的特性</h4>
物件基本上有兩種特性
<ol>
    <li>可識別的</li>
    <li>可移動的</li>
</ol>
可識別的,即物件擁有特定的命名,有指標指向該物件,可以取得該物件位址,例如下面的a:
<pre>int a;
</pre>
可移動的,那我們可直接用該物件,內部不需拷貝,例如下面的3
<pre>int a = 3;
</pre>
<h4 id="lvalue與prvalue">lvalue與prvalue</h4>
lvalue是可識別的,但不具有可移動的特性,我們假如要設定某一個變數a為變數b,a/b都是lvalue,則須要進行拷貝,因為b可能還會在其他地方使用
prvalue具有可移動的特性
<pre>int a = 3;      // ok, 'a' is lvalue and '3' is prvalue
int b = a;      // ok, 'b' is lvalue and 'a' is lvalue
int p = a * b;  // ok, 'p' is lvalue and 'a * b' is prvalue
a * b = 3;      // error, 3 is prvalue, but 'a * b' is also prvalue
                // 換個角度思考:a * b是一個暫時變數也就是prvalue,不可取得該物件位址
                // 也就沒有其記憶體位址,那我們又該如何存3呢
</pre>
<h4 id="lvalue與prvalue在實際程式的運作">lvalue與prvalue在實際程式的運作</h4>
以下參數都使用int,但是請一併想像如果傳遞的是class等等型別,其內容物可能很大,複製的動作無疑相當花費時間:
<pre>void f(int ri) {  // ri是個lvalue
    int b = ri;   // 第二次複製
}
void g() {
    int i = 3;
    f(i);         // 第一次複製
    int d = i;    // ok,i同樣是3,函式f式傳值呼叫
}
</pre>
很顯然的,我們可以透過傳址呼叫來減少呼叫函式f時的拷貝動作:
<pre>void f(int &ri) { // ri是個lvalue
    int b = ri;   // 同樣地,這邊會將ri拷貝給b
}
void g() {
    int i = 3;
    f(i);         // 此次藉由傳址呼叫,將避免一次拷貝
    int d = i;    // ok,i是合法的,但其內的值要看函式f所做的變動
}
</pre>
<h4 id="xvalue與move-semantic">xvalue與move semantic</h4>
現在我們已經知道i是要作為暫時變數的,除了作為函式f的參數外,沒有其他用途,因此我們可以使用一種更為有效率的方式來進行設定,運用move semantic,STL有提供函式move,可將一個lvalue轉換為rvalue reference[3][5],以避免無謂的複製:
<pre>void f(int &ri) {           // 同上,ri是個lvalue
    int b = std::move(ri);  // 這邊呼叫函式move,將ri完美轉送到b
                            // 在這個運算式中,std::move(ri)是個xvalue,不再只是個lvalue
                            // 因為他具有可移動的特性
}
void g() {
    int i = 3;
    f(i);
    int d = i;  // 危險,呼叫函式f後,雖然i是合法的,但其內的值是未定義的
    i = 4;
    int g = i;  // ok,i已經被重新設定了
}
</pre>
<h4 id="總結">總結</h4>
lvalue : 可識別的
xvalue : 可識別的,且可移動
prvalue: 可移動的
glvalue: lvalue / xvalue的總稱,也就是所有可識別的物件
rvalue : prvalue / xvalue的總稱,也就是所有可移動的物件
其關係如下圖所示:
<pre>    lvalue----
              \
                glvalue
              /
    xvalue----
              \
                rvalue
              /
    prvalue---
</pre>
<h3>參考資料</h3>
<ol>
    <li><a href="http://en.cppreference.com/w/cpp/utility/move">move definition</a></li>
    <li>TCPL(The C++ Programming Language) - Bjarne Stroustrup</li>
    <li><a href="http://thbecker.net/articles/rvalue_references/section_01.html">rvalue_references</a></li>
    <li><a href="https://stackoverflow.com/questions/3413470/what-is-stdmove-and-when-should-it-be-used">What is std::move(), and when should it be used?</a></li>
    <li><a href="https://stackoverflow.com/questions/6829241/perfect-forwarding-whats-it-all-about">perfact forwarding</a></li>
</ol>
</article>

arrow
arrow

    Ernest 發表在 痞客邦 留言(1) 人氣()