日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

(原创)C++11改进我们的程序之move和完美转发

發布時間:2023/11/30 c/c++ 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (原创)C++11改进我们的程序之move和完美转发 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://www.cnblogs.com/qicosmos/p/3376241.html

本次要講的是右值引用相關的幾個函數:std::move, std::forward和成員的emplace_back,通過這些函數我們可以避免不必要的拷貝,提高程序性能。move是將對象的狀態或者所有權從一個對象轉移到另一個對象,只是轉移,沒有內存的搬遷或者內存拷貝。如圖所示是深拷貝和move的區別。


  這種移動語義是很有用的,比如我們一個對象中有一些指針資源或者動態數組,在對象的賦值或者拷貝時就不需要拷貝這些資源了。在c++11之前我們的拷貝構造函數和賦值函數可能要這樣定義:
假設一個A對象內部有一個資源m_ptr;

A& A::operator=(const A& rhs) { // 銷毀m_ptr指向的資源 // 復制rhs.m_ptr所指的資源,并使m_ptr指向它 }

同樣A的拷貝構造函數也是這樣。假設我們這樣來用A:

A foo(); // foo是一個返回值為X的函數 A a; a = foo();

最后一行有如下的操作:

  • 銷毀a所持有的資源
  • 復制foo返回的臨時對象所擁有的資源
  • 銷毀臨時對象,釋放其資源

  上面的過程是可行的,但是更有效率的辦法是直接交換a和臨時對象中的資源指針,然后讓臨時對象的析構函數去銷毀a原來擁有的資源。換句話說,當賦值操作符的右邊是右值的時候,我們希望賦值操作符被定義成下面這樣:

A& A::operator=(const A&& rhs) { // 僅僅轉移資源的所有者,將資源的擁有者改為被賦值者 }

  這就是所謂的move語義。再看一個例子,假設一個臨時容器很大,賦值給另一個容器。

{ std::list< std::string > tokens;//省略初始化... std::list< std::string > t = tokens; } std::list< std::string > tokens; std::list< std::string > t = std::move(tokens);

  如果不用std::move,拷貝的代價很大,性能較低。使用move幾乎沒有任何代價,只是轉換了資源的所有權。如果一個對象內部有較大的對內存或者動態數組時,很有必要寫move語義的拷貝構造函數和賦值函數,避免無謂的深拷貝,以提高性能。

完美轉發

  在上一篇的博文中我介紹了右值引用,右值引用類型是獨立于值的,一個右值引用參數作為函數的形參,在函數內部再轉發該參數的時候它已經變成一個左值了,并不是它原來的類型了。因此,我們需要一種方法能按照參數原來的類型轉發到另一個函數,這種轉發被稱為完美轉發。所謂完美轉發(perfect forwarding),是指在函數模板中,完全依照模板的參數的類型,將參數傳遞給函數模板中調用的另外一個函數。c++11中提供了這樣的一個函數std::forward,它是為轉發而生的,它會按照參數本來的類型來轉發出去,不管參數類型是T&&這種未定的引用類型還是明確的左值引用或者右值引用。看看這個例子。

template<typename T> void PrintT(T& t) { cout << "lvaue" << endl; }template<typename T> void PrintT(T && t) { cout << "rvalue" << endl; }template<typename T> void TestForward(T && v) { PrintT(v); PrintT(std::forward<T>(v)); PrintT(std::move(v)); }Test() { TestForward(1); int x = 1; TestForward(x); TestForward(std::forward<int>(x)); }

  測試結果:


  我們來分析一下測試結果:

  • TestForward(1);由于1是右值,所以未定的引用類型T && v被一個右值初始化后變成了一個右值引用,但是在TestForward函數體內部,調用PrintT(v);時,v又變成了一個左值,因為它這里已經變成了一個具名的變量,所以它是一個左值,因此第一個PrintT被調用,打印出"lvaue";PrintT(std::forward<T>(v));由于std::forward會按參數原來的類型轉發,因此,這時它還是一個右值(這里已經發生了類型推導,所以這里的T&&不是一個未定的引用類型,關于這點可以參考我的上一篇講右值引用的博文),所以會調用void PrintT(T &&t)函數。PrintT(std::move(v));是將v變成一個右值引用,雖然它本來也是右值引用,因此它和PrintT(std::forward<T>(v));的輸出結果是一樣的。
  • TestForward(x);未定的引用類型T && v被一個左值初始化后變成了一個左值引用,因此在調用PrintT(std::forward<T>(v));它會轉發到void PrintT(T& t);

萬能的函數包裝器

  右值引用、完美轉發再結合可變模板參數,我們可以寫一個萬能的函數包裝器,它可以接收所有的函數,帶返回值的、不帶返回值的、帶參數的和不帶參數的函數都可以委托這個萬能的函數包裝器執行。看看這個萬能的函數包裝器。

template<class Function, class... Args> inline auto FuncWrapper(Function && f, Args && ... args) -> decltype(f(std::forward<Args>(args)...)) { //typedef decltype(f(std::forward<Args>(args)...)) ReturnType; return f(std::forward<Args>(args)...); //your code; you can use the above typedef. }

再看看測試代碼:

void test0() { cout << "void" << endl; }int test1() { return 1; }int test2(int x) { return x; }string test3(string s1, string s2) { return s1 + s2; }test() { FuncWrapper(test0); //沒有返回值,打印1 FuncWrapper(test1); //返回1 FuncWrapper(test2, 1); //返回1 FuncWrapper(test3, "aa", "bb"); //返回"aabb" }

成員的emplace_back

  c++11中大部分容器都加了一個emplace_back成員函數,vector中它的定義是這樣的:

template< class... Args > void emplace_back( Args&&... args );

  這里的Args&&是一個未定的引用類型,因此它可以接收左值引用和右值引用,它的內部也是調用了std::forward實現完美轉發的。因此如果我們需要往容器中添加右值、臨時變量時,用emplace_back可以提高性能。

c++11 boost技術交流群:296561497,歡迎大家來交流技術。

一點夢想:盡自己一份力,讓c++的世界變得更美好!

總結

以上是生活随笔為你收集整理的(原创)C++11改进我们的程序之move和完美转发的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。