C 右值引用與移動構造

2021-07-03 01:27:45 字數 4696 閱讀 4824

c++中提供了兩種引用方式。左值引用與右值引用。

其中右值引用是c++11的新標準新增的內容。

所謂的右值引用就是必須繫結到右值的引用。

在介紹之前,先說明一下c++中的左值和右值的規定。

實際上,最早在c語言中就有了左值和右值之分。最初的左值即指在賦值號左邊的變數,右值指在賦值號右邊的變數。隨著c語言的發展和c++的出現,重新定義了左值和右值的概念。

可定址的表示式是左值,不可定址的表示式是右值。

這是最為方便的區分左值和右值的方法,舉幾個例子:

#include 

using

namespace

std;

int main()

這段**中,x是可定址的,但是3+4是不可定址的,所以x是乙個左值,3+4是乙個右值表示式。

(一般而言,乙個左值表示式表示的是乙個物件的身份,乙個右值表示式表示的是物件的值)

更多的一般性的例子:

int main()

這裡需要稍微解釋一下最後乙個表示式。

*p是乙個左值,因為可定址。a和b都是乙個左值,但是它們的和是不可定址的,所以a+b是乙個右值表示式。

實際上,更多的右值的例子是臨時物件

還是以*p = a + b為例,a+b得到乙個值返回乙個臨時物件,臨時物件是不可定址的。

這也可以很好的解釋下面的這個例子:

int a = 1

;cout << &(++a) << endl;

cout << &(a++) << endl;

a++是先返回乙個a的拷貝,是乙個臨時物件,所以這是乙個右值,不可取址,這樣編譯器會提示錯誤。

++a是直接將a增加1後返回左值引用,是可定址的。

同理,因為許多函式的返回物件都是臨時物件,所以均為不可定址的右值。

int max(int x, int y)

這裡雖然x和y傳遞進來都是左值,但是返回值卻是乙個臨時物件,不可定址,所以是右值。

右值引用的加入是為了實現移動語義

若要更好地了解移動語義,思考下面這個例子。

#include 

using

namespace

std;

vector

doublevalues (const

vector

& v)

return new_values;

}int main()

v = doublevalues( v );

}

在doublevalues的呼叫中,會發生兩次拷貝

第一次是函式體重的new_values

第二次是v = doublevalues(v)的operator=()

更可氣的是在賦值完畢後臨時物件又被銷毀了,大大浪費記憶體和時間

有一種避免多次拷貝的辦法是返回乙個引用,不過這樣還是需要在函式中動態分配記憶體,但是c++要求盡量簡化操作,不分配記憶體。

這時候,移動語義就派上用場了,直接移動元素,避免多次記憶體分配銷毀,大大提高了效率。

右值引用能夠實現移動語義是因為右值的重要性質——只能繫結到乙個將要銷毀的物件。而且右值引用有一點和右值需要區別開來,就是右值引用是可取址的:

int && r = 3;

cout

<< &r << endl;

這樣可行的原因是,編譯器將已命名的右值引用視為左值。當右值引用出現在函式的引數列表中時,也會發生此類情況。

右值引用同左值引用一樣,必須在定義的時候進行初始化。以&&為右值引用的識別符號。

int fun(int x)

int && r1 = 3 + 4 + 7;

int && r2 = fun(3);

int && r3 = r1 + r2;//r1+r2產生臨時物件是乙個右值

const

int & cl1 = fun(3);//const左值引用也可以繫結乙個右值!

const

int & cl2 = r3;

cout

<< "r1 : "

<< r1 << endl;

cout

<< "r2 : "

<< r2 << endl;

cout

<< "r3 : "

<< r3 << endl;

cout

<< "cl1 : "

<< cl1 << endl;

cout

<< "cl2 : "

<< cl2 << endl;

再強調一遍,編譯器將已命名的右值引用視為左值

顯然我們不能直接將右值引用繫結到乙個左值上,但是利用標準庫函式std::move(obj)我們可以將乙個左值(obj)轉換為對應的右值引用

int && r = std::move(lerf_value);
move函式告訴編譯器,我這裡有乙個左值,但是我希望像乙個右值一樣(短暫存在,右值引用的物件即將被銷毀,該物件沒有其他使用者使用)處理它。這意味著,當使用move函式時,我們對編譯器承諾除了賦值銷毀之外,我們不會再使用它。

並且,在呼叫move之後,我們不能對「移後源」(obj)的值做出任何假設。由於移後源的這種不確定狀態,使用move是危險的,一定要確保obj沒有其他的使用者!

實際上,move就是static_cast(obj)

前面講到加入移動語義是為了更快的將元素直接移動到新的記憶體當中從而減少拷貝次數,提公升程式的效能。

那麼給出msdn的例子,說明如何編寫移動建構函式。

// memoryblock.h

#pragma once

#include

#include

class memoryblock

// destructor.

~memoryblock()

std::cout

<< std::endl;

}// copy constructor.

memoryblock(const memoryblock& other)

: _length(other._length)

, _data(new

int[other._length])

// copy assignment operator.

memoryblock& operator=(const memoryblock& other)

return *this;

}// retrieves the length of the data resource.

size_t length() const

private:

size_t _length; // the length of the resource.

int* _data; // the resource.

};

這是未新增移動建構函式版本的。

移動建構函式與移動賦值運算子他們的型別都是右值引用(不是const)。

// move constructor.

memoryblock(memoryblock&& other)

: _data(null)

, _length(0)

移動建構函式不分配任何新記憶體,直接接管給定的物件中的記憶體資源。在接管後,保證移後源的各資料成員都處於一種可析構的狀態。如果是指標則賦值為空防止多次析構

// move assignment operator.

memoryblock& operator=(memoryblock&& other)

return *this;

}

移動賦值建構函式同理。

編譯器也會合成預設的移動建構函式和移動賦值運算子,不過,合成條件與拷貝建構函式大不相同。

只要乙個類定義了自己的copy建構函式,copy assign運算子或者析構函式,編譯器就不會合成預設的移動建構函式和移動賦值運算子。

不過,很有意思的是,如果沒有移動建構函式的情況下,還是使用了std::move()函式,編譯是可以通過的。但是此時使用的是copy建構函式,這是因為&&可以隱式轉換為const &.

例如:

class foo

//沒有定義移動建構函式,也不會生成

};int main()

右值引用,移動構造,移動賦值

1 目的 右值引用,移動構造和移動賦值是在c 11中引入的,其目的是為了提公升 效率 2 使用場景 類中如果有需要申請動態資源的成員,那麼定義移動建構函式和移動賦值運算子可以避免不必要的拷貝工作,從而提公升 效率。移動構造和移動賦值並不會新開闢資源,而是將源物件的一部分或全部資源移交給了新物件。st...

右值引用與移動建構函式 移動賦值

有一陣子沒看c 了,翻開c primer又陌生了一些,想了想引用,於是乎來看了下右值引用。int a 5 int b a 這是左值引用,若我們這樣修改 int a 5 int b a 1 編譯器會報錯 非常量引用的初始值必須為左值。也就是右邊的a 1是常量,常量給非常量引用賦值就報錯。我們可以這樣修...

C 右值引用與移動操作

右值引用和移動操作是c 11提出的新概念,通過這些操作,可以降低拷貝操作帶來的消耗。先來簡單介紹一下左值和右值。左值一般指的是乙個物件,或者說是乙個持久的值,例如賦值的返回值 下標操作 解引用以及前置遞增等。右值是乙個短暫的值,比如乙個表示式的求值結果 函式返回值以及乙個字面值等。為了區分,把一般的...