[讀書筆記]Universal References in C++11 part 1: rvalue and lvalue

參考文章:

C++ Rvalue References Explained By Thomas Becker

Universal References in C++11 by Scott Meyers

 

寫在前面:

這個系列的程式碼與例子大多出自以上二篇文章,再加上自己本身的理解所描述而成,因此可能會有謬誤的地方還請見諒。若有什麼意見或想法,非常歡迎討論與指教。

本文開始

首先讓我們來看看下面這一段code:

template <typename T>
void f(T&& param);

我們知道T&指的pass by eference,但T&&到底是?pass by reference的reference?就語法的理解上,似乎有點不太明確的知道它到底在做什麼,因為C++並沒有這種概念。為了跳脫這個語法表示上理解的困境,我們並不需要糾結於「&」這個語法的意義上,「&&」這東西並不是字面上是pass by reference的reference,就把「&&」他當成新的語意來理解吧!

其實T&&這個語法,是在C++11的時候才導入的,它主要可以解決以下幾個問題:

  1. Implementing move semantics
  2. Perfect forwarding

這裡先還不用急著理解這二個問題到底是什麼,下面的文章將會入深入的討論。但我們只要先知道:「在解決這二個問題之後,在程式中傳遞參數將能夠省下許多記憶體copy的時間」ーー這件事就行了。簡單來說,就是你的程式會變得更快。它能夠保證讓任何type的參數傳入可以用pass by reference的形式來處理,藉以減少傳遞參數時必須要從記憶體copy一份到新的參數上的開銷。Scott Meyers用Universal References來形容T&&這個語法實在是很巧妙,因為這個命名精確的傳達出了它的邏輯與用途。(c++11標準是沒有對它命名的,只有描述)

  但在開始深入探討Universal References之前,我們必須先理解何謂左值(lvalue)與右值(rvalue)。在這裡我並不打算做詳細的定義,因此只會給個簡單的概念性描述:所謂的左值(lvalue),指的就是在賦與(assign)變數數值的時候,能夠出現在左邊或右邊的變數;而右值(rvalue),則是只能出現在右邊的數字。換另外一種說法就是,lvalue能夠接受並儲存一個數字,並將這個數字賦與給其他的lvalue;而rvalue則是只能將該值賦與給其他的lvalue。請參考下面的code:

  
  int a = 42;
  int b = 43;

  // a and b are both l-values:
  a = b; // ok
  b = a; // ok
  a = a * b; // ok

  // a * b is an rvalue:
  int c = a * b; // ok, rvalue on right hand side of assignment
  a * b = 42; // error, rvalue on left hand side of assignment

在這裡a跟b是左值(lvalue),因此能夠出現在賦值符號(=)的左邊及右邊,但42、43以及(a*b)是右值(rvalue),因此只能出現在右邊。這裡比較需要思考的是,為什麼a跟b是左值,但a*b卻是右值。這是因為運算符號*其實也就是一個接受二個參數的function而已,如: int operator*(int left, int right);。function 無法賦值,因此是rvalue。當然,如果你取的是這個function回傳值的reference的話,那麼它又會是lvalue……。情況似乎變的有點複雜,讓我們來看看以下這篇文章的討論:http://stackoverflow.com/questions/13312817/foo-42-how-can-this-be-possible,裡面提到的例子:

int & foo()
{
   static int ttt = 0;
   return ttt;
}


// lvalues:
//
int i = 42;
i = 43; // ok, i is an lvalue
int* p = &i; // ok, i is an lvalue
int& foo();
foo() = 42; // ok, foo() is an lvalue
int* p1 = &foo(); // ok, foo() is an lvalue

首先先把i設為42,再設為43,然後將i的reference指給p,這時p的任何更動就等同於更動i(以作業系統來看就是hard link的概念)。這時候有趣的東西來了:『int& foo();  』。這到底是什麼意思?其實就是取foo回傳變數的reference。下一行當我們執行『foo() = 42』時,就等同於把foo裡面的ttt設為42。最後一行,就是把ttt的的reference指給p1。

當然,上面這個例子我們特別必須要注意到的就是ttt這個變數的生命週期了。若該變數沒有加上static的話,猜猜看會發生什麼事?ーー我不知道,當你試著去對一個記憶體已經被釋放的ttt(dangling reference)進行操作的時候會怎麼樣?大概,程式會死在那邊給你看吧?亦或者說那段時間沒有人去用到那塊記憶體,程式『一如往常』的執行,然後又非週期性的大爆炸。又或者說,有別的指標去指到那塊4byte的記憶體,結果程式去執行該指標所指到的function。ーー總之,那是個災難的開始。undefined behaviour,指的就是這種情況。若你的程式中曾經出現過這種薛丁格或海森堡式的bug時,可能要去思考一下是不是有dangling reference的存在。

dorgon

dorgon

職業:LV3遊戲軟體工程師 為了追尋小時候玩遊戲的感動,而一頭栽入遊戲業界。 本來以撰寫遊戲劇本為主要志向,但回過神來才發現已經踏入程序猿的不歸路。 專長為client端跨平台遊戲開發架構與自動建置流程,主要使用引擎為cocos2d-x與UnrealEngine4。

More Posts - Website

Follow Me:
FacebookLinkedIn

有什麼想法嗎?請發表你的看法