1. 程式人生 > >13.6對象移動

13.6對象移動

call 沒有 sse mys 標準庫 引用 語句 符號 持久

1.左值和右值
左值:非臨時對象,可以在多條語句裏面使用的對象。
右值:臨時對象,只能在本條語句裏面使用。
如:int i = 0;//i是持久對象,能在多條語句裏面使用,0是臨時對象,只能在本條語句裏面使用
2.左值引用和右值引用
在C++11以前,右值不能被引用,最大限度就是用常量引用綁定一個右值:const int &a = 1
左值引用:&
右值引用:&&
如下:
void process_value(int& i) {
    std::cout << "LValue processed: " << i << std::endl;
}
void process_value(int&& i) { std::cout << "RValue processed: " << i << std::endl; } int main() { int a = 0; process_value(a);//左值 process_value(1);//右值 } 結果為: LValue processed: 0 RValue processed: 1 3.移動構造函數和移動賦值運算符 註意:移動操作通常不應該拋出異常,不拋出異常的移動構造函數和移動賦值運算符函數後面應該標記為noexcept,在聲明和定義都必須指定。 如下代碼實現了拷貝構造和賦值運算符:
class MyString { private: char* _data; size_t _len; void _init_data(const char *s) { _data = new char[_len+1]; memcpy(_data, s, _len); _data[_len] = \0; } public: MyString() { _data = NULL; _len = 0; } MyString(const char* p) { _len
= strlen (p); _init_data(p); } MyString(const MyString& str) { _len = str._len; _init_data(str._data); std::cout << "Copy Constructor is called! source: " << str._data << std::endl; } MyString& operator=(const MyString& str) { if (this != &str) { _len = str._len; _init_data(str._data); } std::cout << "Copy Assignment is called! source: " << str._data << std::endl; return *this; } virtual ~MyString() { if (_data) free(_data); } }; int main() { MyString a; a = MyString("Hello”);//先調用構造函數再調用賦值運算符 vector<MyString> vec; vec.push_back(MyString("World"));//先調用構造函數再調用拷貝構造函數 } 運行結果為: Copy Constructor is called! source: Hello Copy Assignment is called! source: World * 這段代碼中,由於MyString(“Hello”)和MyString(“World”)都是臨時對象,但依然調用了拷貝構造函數和賦值運算符,造成了不必要的空間和性能上不損失,所以,移動構造函數和移動賦值就是直接使用臨時對象所申請的資源。 改變後的代碼如下: //移動構造函數 MyString(MyString&& str) { std::cout << "Move Constructor is called! source: " << str._data << std::endl; _len = str._len; _data = str._data; str._len = 0; str._data = NULL; } //移動賦值運算符 MyString& operator=(MyString&& str) { std::cout << "Move Assignment is called! source: " << str._data << std::endl; if (this != &str) { _len = str._len; _data = str._data; str._len = 0; str._data = NULL; } return *this; } 註意: * 參數(右值)的符號必須是右值引用符號,&& * 參數(右值)不可以是常量,因為需要修改右值 * 參數(右值)的資源鏈接和標記必須修改,否則,右值的析構函數會釋放資源,轉移到新對象的資源就會無效。 運行結果: Move Assignment is called! source: Hello Move Constructor is called! source: World 因此,可以清楚知道移動構造函數不分配任何內存,只是接管對象的內存,接管後將源對象指針置為nullptr,最終移後源對象會被銷毀,也就是會調用析構函數。如果不改變源對象的指向,就會釋放掉移動的內存。移動賦值函數也應該不拋出任何異常,並且應該處理自賦值。 移動一定會改變源對象的值 4.std::move()函數 move()函數在標準庫utility中,可以將左值轉化為右值,如:int &&rval = std::move(lval); move調用告訴編譯器,有一個左值,但是我們想像右值一樣處理它。調用move就意味著承諾:除了對lval賦值和銷毀外,將不再使用它。 註意:使用這個函數後,lval不能再被使用,只能使用rval,特別的move()函數對於swap操作性能提升很大,由於一個移後源對象具有不確定狀態,調用move()是危險的。當調用move()時,必須確認移動源對象沒有其他用戶。 int main() { string str = "Hello"; vector<std::string> v; v.push_back(str); cout << "After copy, str is \"" << str << "\"\n"; v.push_back(std::move(str)); cout << "After move, str is \"" << str << "\"\n"; cout << "The contents of the vector are \"" << v[0] << "\", \"" << v[1] << "\"\n"; return 0; } 輸出結果為: After copy, str is “Hello” After move, str is “” The contents of the vector are “Hello”, “Hello” 5.合成的移動操作 當一個類定義了自己的拷貝構造函數,拷貝賦值運算符或者析構函數,編譯器就不會合成移動構造函數和移動賦值運算符。 只有當一個類沒有定義任何自己版本的拷貝控制成員,且類的每個非static數據成員都可以移動時,編譯器才會為它合成移動構造函數或移動賦值運算符。 移動操作不會隱式定義為刪除的函數,但是如果顯式要求編譯器生成=default移動操作,且編譯器不能移動所有成員,則編譯器會將移動操作定義為刪除的函數。 * 有類成員定義了自己的拷貝構造函數且為定義移動構造函數,或者是有類成員未定義自己的拷貝構造函數且編譯器不能為其合成移動構造函數。移動賦值運算符類似,移動操作會被定義為刪除。 * 如果有類成員的移動構造函數或移動賦值運算符被定義為刪除的或是不可訪問的,則移動操作被定義為刪除。 * 如果類的析構函數被定義為刪除的或不可訪問的,則移動構造函數被定義為刪除。 * 如果有類成員是const或引用,則移動賦值運算符被定義為刪除。 6.調用方式 如果定義了移動操作和拷貝控制,編譯器會自動匹配。 但是如果沒有定義移動操作,那麽就算是使用了move()函數,也只會調用拷貝構造函數。 如下: class Foo{ public: Foo() = default; Foo(const Foo&);//拷貝構造函數 }; int main() { Foo x; Foo y(x); //調用拷貝構造函數 Foo z(move(x));//依然調用拷貝構造函數 return 0; } 7.三五法則 新標準下,類的五種操作:拷貝構造函數,拷貝賦值運算符,移動構造函數,移動賦值運算符,析構函數。只要其中一個定義了,那麽其他的幾個都應該定義。

13.6對象移動