1. 程式人生 > 其它 >C++ 右值引用和移動語義

C++ 右值引用和移動語義

左值

左值:一個表示資料的表示式(如變數名或者解除引用的指標),程式可從堆疊上獲取其地址。最初,左值可出現在賦值語句的左邊,但是在有了const修飾符後,可以宣告一個沒有賦值的識別符號並獲取其地址。

//左值的例子
int n;
int *pt = new int;
const int b = 101;
int & rn = n;
int & rt = *pt;
const int & rb = b;

int &var = 10; // error
const int &var = 10; // right  (const修飾符後,宣告一個沒有賦值的識別符號並獲取其地址)

int func(){
  int a = 10;
  return a;
}
int &var1 = func(); // error
cosnt int &var1 = func();  // right (但我們想要之後修改var1的值就無法修改了)

左值引用要求右邊的值必須能夠取地址,如果無法取地址,可以用常引用;如最後兩個例子所示;
但使用常引用後,我們只能通過引用來讀取資料,無法去修改資料,因為其被const修飾成常量引用了。
故C++11新增了右值引用;

右值

右值包括字面常量(c-風格字串除外,他表示地址)、諸如x+y等表示式以及函式的返回值(函式的返回值不是引用);

int x = 10;
int y = 23;
int &&r1 = 13;
int &&r2 = x+y;  // (r2關聯到的是23,即使後來修改了x、y,也不會使r2變化)
double &&r3 = std::sqrt(2.0);

移動語義

vector<int> getVector(int num){
  return vector<int>(num,0);
}

int main(){
  vector<int> res = getVector(1000); // 移動語義
}

在上面一個例子,其實已經使用到了移動語義vector<int>(vector<int> &&v)
但如果我們只考慮普通的拷貝建構函式,那麼就需要將這個陣列在返回值(指的是返回的臨時物件而非res)的基礎上在拷貝一份,然後再將返回值拋棄,做了大量的無用功;那麼我們為何不直接把返回值中的資料的所有權直接移動到res中。
要實現移動語義,需要採取某種方式,讓編譯器知道什麼時候需要賦值,什麼時候不需要,而右值引用此時發揮了作用。

class U{
public:
  U(const U& f);  // 拷貝建構函式
  U(U && f);   // 移動建構函式

  U &operator=(const U& f)const;   // 拷貝賦值
  U &operator=(U &&f);            // 移動賦值
  U operator+(const U&f)const;
};

int main(){
  U u1;
  U u2 = u1;  //拷貝建構函式
  U u3(u1+u2);  //移動建構函式 
}

強制移動

通常移動建構函式和移動運算子使用右值,但一定要他們使用左值的時候,可以使用std::move()

// 如我們需要在一個候選陣列中挑一個物件使用,並丟棄陣列;
U choices[10];
U best;
ini pick;
// ... 進行選擇, 將pick設定為目標索引
best = std::move(choices[pick]);

特殊的成員函式

C++11中特殊的成員函式有6個

  1. 預設建構函式
  2. 拷貝建構函式
  3. 拷貝賦值運算子
  4. 移動建構函式
  5. 移動複製運算子
  6. 解構函式

可以使用default設定預設方法和使用delete禁用方法

class Someclass{
public:
  Someclass()=default;  // 使用編譯器給定的預設構造
  Someclass(const Someclass &)=delete;  // 禁止複製物件
  Someclass & operator=(const Somelcass &)=delete;  // 禁止複製物件
...
};