1. 程式人生 > >物件移動

物件移動

在重新分配記憶體的過程中,從舊記憶體將元素拷貝到新記憶體是不必要的,更好的方式是移動元素。使用移動而不是拷貝的另一個原因源於IO類或unique_ptr這樣的類。這些類包含不能被共享的資源(如指標或IO緩衝),因此,這些型別的物件不能拷貝,但是可以移動。

標準庫容器、string和shared_ptr類既支援移動也支援拷貝。IO類和unique_ptr類可以移動但不能拷貝。

右值引用

右值引用是必須繫結到右值的引用,通過&&來獲得右值引用。右值引用只能繫結到一個將要銷燬的物件。因此可以自由地將一個右值引用的資源“移動”到另一個物件中。

一個左值表示一個物件的身份,一個右值表示物件的值。

左值引用:不能將其繫結到要求轉換的表示式、字面值常量或是返回右值的表示式。

右值引用:與左值引用相反,可以繫結到這類表示式上。

返回左值引用的函式,連同賦值、下標、借用用和前置遞增/遞減運算子,都是返回左值的表示式的例子。

返回非引用型別的函式,連同算術、關係、位以及後置遞增/遞減運算子都生成右值。我們不能將一個左值引用繫結到這類表示式上,但我們可以將一個const的左值引用或者一個右值繫結到這類表示式上。

左值持久;右值短暫

右值引用指向將要被銷燬的物件。因此,我們可以從繫結到右值引用的物件“竊取”狀態

變數是左值

變數是左值,因此我們不能將一個右值引用直接繫結到一個變數上,即使這個變數是右值引用型別也不行。

標準庫move函式

可以顯式地將一個左值轉換為對應的右值引用型別。通過呼叫一個名為move的新標準庫函式來獲得繫結到左值上的右值引用。move定義在utility標頭檔案中。

我們可以銷燬一個移後源物件,也可以賦予它新值,但不能使用一個移後源物件的值。

移動建構函式和移動賦值運算子

StrVec::StrVec(StrVec &&s) noexcept   //移動操作不應該丟擲任何異常

//成員初始化器接管s中的資源

  :elements(s.elements),first_free(s.first_free),cap(s.cap)

{

//另s進入這樣一種狀態——對其進行解構函式是安全的

s.elements=s.first_free=s.cap=nullptr;

}

第一個引數是該類型別的右值引用。除了完成資源移動,移動建構函式還必須保證移後源物件處於這樣一個狀態——銷燬它是無害的。

noexcept 通知標準庫我們的建構函式不丟擲任何異常。

由於移動操作“竊取”資源,它通常不分配任何資源,因此移動操作通常不會丟擲任何異常。noexcept是我們承諾一個函式不丟擲異常的一種方法。

不丟擲異常的移動建構函式和移動賦值運算子必須標記為noexcept.

移動賦值運算子

如果右側和左側運算物件指向相同的物件,不需要做任何事情;否則,釋放左側物件所使用的記憶體,並接管給定物件的記憶體。

不能使用右側運算物件之前就釋放左側運算物件資源(可能是相同的資源)

移後源物件必須可析構

在移動操作之後,移後源物件必須保持有效的、可析構的狀態,但使用者不能對其值進行任何假設。

合成的移動操作

如果一個類定義了自己的拷貝建構函式、拷貝賦值運算子或者解構函式,編譯器不會為它合成移動建構函式和移動賦值運算子。如果一個類沒有移動操作,通過正常的函式匹配,類會使用對應的拷貝操作來代替移動操作。

只有當一個類沒有定義任何自己版本的拷貝控制成員,且類的每個非static資料成員都可以移動時,編譯器才會為它合成移動建構函式或移動賦值運算子。編譯器可以移動內建型別成員。

與拷貝控制不同,移動操作永遠不會隱式定義為刪除的函式。

如果我們顯式地要求編譯器生成=default的移動操作,且編譯器不能移動所有成員,則編譯器會將移動操作定義為刪除的函式。

定義了一個移動建構函式或移動賦值運算子的類必須也定義自己的拷貝控制操作。

移動右值,拷貝左值

但如果沒有移動建構函式,右值也被拷貝

如果一個類有一個可用的拷貝建構函式而沒有移動建構函式,則其物件是通過拷貝建構函式來“移動”的。拷貝賦值函式和移動賦值函式類似。

賦值運算子既是移動賦值運算子,也是拷貝賦值運算子

更新三/五法則

所有五個拷貝控制成員應該被看作一個整體:一般來說,如果一個類定義了任何一個拷貝操作,它就應該定義所有五個操作。如前所述,某些類必須定義拷貝建構函式、拷貝賦值運算子和解構函式才能正確工作。這些類通常擁有一個資源,而拷貝成員必須拷貝此資源。一般來說,拷貝一個資源會導致一些額外開銷。在這種拷貝並非必要的情況下,定義了移動建構函式和移動賦值運算子的類可以避免此問題。

移動迭代器

一個移動迭代器通過改變給定迭代器的解引用運算子的行為來適配此迭代器。移動迭代器的解引用運算子生成一個右值引用。

呼叫標準庫的make_move_iterator函式將一個普通迭代器轉換為一個移動迭代器。

不要隨意使用移動操作

由於一個移後源物件具有不確定的狀態,對其呼叫std::move是危險的。當我們呼叫move時,必須絕對確認移後源物件沒有其他使用者。

在移動建構函式和移動賦值運算子這些類實現程式碼之外的地方,只有當你確信需要進行移動操作且移動操作時安全的,才使用std::move。

 

右值引用和成員函式

如果一個成員函式同時提供拷貝和移動版本,它也能從中受益

這種允許移動的成員函式通常使用與拷貝/移動建構函式和賦值運算子相同的引數模式——一個版本接受一個指向const的左值引用,第一個版本接受一個指向非const的右值引用。

class StrVec{
public:
void push_back(const std::string&);   //拷貝元素
void push_back(std::string&&);        //移動元素
}
void StrVec::push_back(const string& s)
{
chk_n_alloc();
alloc.construct(free_first++,s);
}
void StrVec::push_back(const string&& s)
{
chk_n_alloc();
alloc.construct(free_first++,std::move(s));
}

 

右值和左值引用成員函式

指出this的左值/右值屬性與const成員函式相同。即,在引數列表後面放置一個引用限定符

class Foo{
public:
Foo &operator=(const Foo&) &;  //只能向可修改的左值賦值
};
Foo &Foo::operator=(const Foo &rhs) &
{
//執行將rhs賦予本物件所需的工作
return *this;
}

 

引用限定符可以是&或&&,分別指出this可以指向一個左值或一個右值。

一個函式可以同時用const和引用限定。在此情況下,引用限定符必須跟隨在const限定符之後。

 

區分移動和拷貝的過載函式通常由一個版本接受一個const T&,而另一個版本接受一個T&&。