T &&(雙「&」號)在C ++ 11中是什麼意思?

我一直在研究C ++ 11的一些新功能,我注意到的一個是在聲明變量(例如T&& var )時使用雙「&」號。 html

首先,這隻野獸叫什麼? 我但願Google容許咱們搜索這樣的標點符號。 express

這究竟是什麼意思? 函數

乍一看,它彷佛是一個雙重引用(例如C風格的雙重指針T** var ),可是我很難考慮到這種狀況。 this


#1樓

T&&一詞在與類型推導一塊兒使用 (例如用於完美轉發)時,一般被稱爲轉發參考 。 術語「通用引用」由Scott Meyers 在本文中提出 ,但後來被更改。 spa

那是由於它能夠是r值或l值。 .net

例如: 指針

// template
template<class T> foo(T&& t) { ... }

// auto
auto&& t = ...;

// typedef
typedef ... T;
T&& t = ...;

// decltype
decltype(...)&& t = ...;

能夠在如下答案中找到更多討論: 通用引用的語法 code


#2樓

右值引用是一種行爲,其行爲與普通引用X&類似,但有一些例外。 最重要的是,對於函數重載解析,左值更喜歡舊式左值引用,而右值更喜歡新的右值引用: orm

void foo(X& x);  // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar();

foo(x);        // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

那麼什麼是右值? 任何不是左值的東西。 左值是表示存儲位置的表達式,它容許咱們經過&運算符獲取該存儲位置的地址。 htm

首先經過一個示例來了解右值能夠完成的工做:

class Sample {
  int *ptr; // large block of memory
  int size;
 public:
  Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
  {}
  // copy constructor that takes lvalue 
  Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
      nullptr}, size{s.size}
  {
     std::cout << "copy constructor called on lvalue\n";
  }

  // move constructor that take rvalue
  Sample(Sample&& s) 
  {  // steal s's resources
     ptr = s.ptr;
     size = s.size;        
     s.ptr = nullptr; // destructive write
     s.size = 0;
     cout << "Move constructor called on rvalue." << std::endl;
  }    
  // normal copy assignment operator taking lvalue
  Sample& operator=(const Sample& s)
  {
   if(this != &s) {
      delete [] ptr; // free current pointer
      ptr = new int[s.size]; 
      size = s.size; 
    }
    cout << "Copy Assignment called on lvalue." << std::endl;
    return *this;
  }    
 // overloaded move assignment operator taking rvalue
 Sample& operator=(Sample&& lhs)
 {
   if(this != &s) {
      delete [] ptr; //don't let ptr be orphaned 
      ptr = lhs.ptr;   //but now "steal" lhs, don't clone it.
      size = lhs.size; 
      lhs.ptr = nullptr; // lhs's new "stolen" state
      lhs.size = 0;
   }
   cout << "Move Assignment called on rvalue" << std::endl;
   return *this;
 }
//...snip
};

構造函數和賦值運算符已被帶有右值引用的版本重載。 右值引用容許函數在編譯時(經過重載解析)在條件「是否在左值或右值上調用我?」時分支。 這使咱們可以在上面建立更有效的構造函數和賦值運算符,從而移動資源而不是複製資源。

編譯器在編譯時自動分支(取決因而爲左值仍是右值調用它),選擇是否應調用move構造函數或move賦值運算符。

總結:右值引用容許移動語義(以及完美的轉發,在下面的文章連接中討論)。

一個容易理解的實用示例是類模板std :: unique_ptr 。 因爲unique_ptr維護其基礎原始指針的專有全部權,所以沒法複製unique_ptr。 這將違反其專有權的不變性。 所以,它們沒有副本構造函數。 可是他們確實有移動構造函數:

template<class T> class unique_ptr {
  //...snip
 unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};

 std::unique_ptr<int[] pt1{new int[10]};  
 std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.  

 // So we must first cast ptr1 to an rvalue 
 std::unique_ptr<int[]> ptr2{std::move(ptr1)};  

std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
 int size)      
{
  for (auto i = 0; i < size; ++i) {
     param[i] += 10;
  }
  return param; // implicitly calls unique_ptr(unique_ptr&&)
}

// Now use function     
unique_ptr<int[]> ptr{new int[10]};

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
           static_cast<unique_ptr<int[]>&&>(ptr), 10);

cout << "output:\n";

for(auto i = 0; i< 10; ++i) {
   cout << new_owner[i] << ", ";
}

output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10,

static_cast<unique_ptr<int[]>&&>(ptr)一般使用std :: move完成

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);

托馬斯·貝克爾(Thomas Becker)的C ++ Rvalue References Explained是一篇出色的文章,其中包括不少很好的例子,對全部這些內容(包括rvalue如何實現完美的轉發以及這意味着什麼)進行了解釋 。 這篇文章主要依靠他的文章。

簡短的介紹是Stroutrup等人撰寫的「 右值引用簡介」 。 人


#3樓

它聲明一個右值參考 (標準建議文檔)。

這是右值引用的簡介

這是Microsoft標準庫開發人員之一對rvalue引用進行的精彩深刻研究。

注意: MSDN上的連接文章(「 Rvalue參考:VC10中的C ++ 0x功能,第2部分」)是對Rvalue引用的很是清晰的介紹,可是對有關Rvalue引用的聲明在C ++ 11草案中曾經是正確的。標準,但對於最後一個不是正確的! 具體來講,它說在各個點上右值引用能夠綁定到左值,這曾經是真實的,可是已經被更改。(例如int x; int && rrx = x;再也不在GCC中編譯)– drewbarbs 2014年7月13日在16:12

C ++ 03引用(在C ++ 11中如今稱爲左值引用)之間的最大區別在於,它能夠像臨時元素同樣綁定到右值,而沒必要使用const。 所以,此語法如今合法:

T&& r = T();

右值引用主要提供如下內容:

移動語義 。 如今能夠定義移動構造函數和移動賦值運算符,該運算符采用右值引用而不是一般的const-左值引用。 移動的功能相似於副本,只是它沒有義務保持源不變。 實際上,它一般會修改源,使其再也不擁有已移動的資源。 這對於消除無關的副本很是有用,尤爲是在標準庫實現中。

例如,複製構造函數可能以下所示:

foo(foo const& other)
{
    this->length = other.length;
    this->ptr = new int[other.length];
    copy(other.ptr, other.ptr + other.length, this->ptr);
}

若是將此構造函數傳遞給臨時對象,則不須要複製,由於咱們知道臨時對象將被銷燬。 爲何不利用已分配的臨時資源? 在C ++ 03中,因爲沒法肯定咱們是否被臨時傳遞,所以沒法阻止複製。 在C ++ 11中,咱們能夠重載move構造函數:

foo(foo&& other)
{
   this->length = other.length;
   this->ptr = other.ptr;
   other.length = 0;
   other.ptr = nullptr;
}

注意這裏的最大區別:move構造函數其實是修改其參數。 這將有效地將臨時文件「移動」到正在構造的對象中,從而消除了沒必要要的複製。

move構造函數將用於臨時對象和很是量左值引用,這些引用使用std::move函數(僅執行轉換)顯式轉換爲右值引用。 如下代碼均調用f1f2的move構造函數:

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

完美的轉發 。 右值引用使咱們可以正確轉發模板函數的參數。 以這個工廠功能爲例:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
    return std::unique_ptr<T>(new T(a1));
}

若是咱們調用factory<foo>(5) ,則將推導該參數爲int& ,即便foo的構造函數採用int ,該參數也不會綁定到文字5。 好吧,咱們能夠改用A1 const& ,可是若是foo經過非const引用接受構造函數參數怎麼辦? 爲了實現真正的通用工廠功能,咱們必須在A1&A1 const&上重載工廠。 若是factory採用1個參數類型,可能會很好,可是每一個其餘參數類型都會將必需的重載設置乘以2。這很快就沒法維護。

右值引用經過容許標準庫定義能夠正確轉發左值/右值引用的std::forward函數來解決此問題。 有關std::forward工做方式的更多信息,請參見此出色的答案

這使咱們可以定義工廠功能,以下所示:

template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
    return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}

如今,當傳遞給T的構造函數時,參數的rvalue / lvalue-ness得以保留。 這意味着,若是使用rvalue調用factory,則使用rvalue調用T的構造函數。 若是使用左值調用factory,則使用左值調用T的構造函數。 改進的工廠功能之因此有效,是由於如下一條特殊規則:

當函數參數類型的形式爲T&& ,其中T是模板參數,而且函數參數是類型A的左值時,類型A&用於模板參數推導。

所以,咱們能夠像這樣使用工廠:

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

重要的右值參考屬性

  • 對於重載解析, 左值傾向於綁定到左值引用,而右值傾向於綁定到右值引用 。 所以,爲何臨時人員比複製構造函數/賦值運算符更喜歡調用移動構造函數/移動賦值運算符。
  • 右值引用將隱式綁定到右值和做爲隱式轉換結果的臨時對象 。 即float f = 0f; int&& i = f; float f = 0f; int&& i = f; 格式良好,由於float能夠隱式轉換爲int; 該引用將是轉換後的臨時結果。
  • 命名的右值引用是左值。 未命名的右值引用是右值。 瞭解爲何爲何必須在如下位置進行std::move調用很是重要: foo&& r = foo(); foo f = std::move(r); foo&& r = foo(); foo f = std::move(r);

#4樓

它表示右值參考。 右值引用將僅綁定到臨時對象,除非以其餘方式明確生成。 它們用於使對象在某些狀況下更加高效,並提供一種稱爲「完美轉發」的功能,該功能大大簡化了模板代碼。

在C ++ 03中,您沒法區分非可變左值和右值的副本。

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);

在C ++ 0x中,狀況並不是如此。

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);

考慮這些構造函數背後的實現。 在第一種狀況下,字符串必須執行復制以保留值語義,這涉及新的堆分配。 可是,在第二種狀況下,咱們預先知道傳遞給咱們的構造函數的對象將當即被銷燬,而且沒必要保持不變。 在這種狀況下,咱們能夠有效地交換內部指針而根本不執行任何複製,這實際上要更有效率。 移動語義可使任何昂貴或禁止複製內部引用資源的類受益。 考慮一下std::unique_ptr的狀況-如今咱們的類能夠區分臨時對象和非臨時對象,咱們可使移動語義正確運行,從而不能複製unique_ptr而是能夠移動它,這意味着std::unique_ptr能夠能夠合法地存儲在Standard容器中,進行排序等,而C ++ 03的std::auto_ptr不能。

如今,咱們考慮右值引用的另外一種用法-完美轉發。 考慮將引用綁定到引用的問題。

std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template

記不清C ++ 03所說的內容,可是在C ++ 0x中,處理右值引用時的結果類型相當重要。 對類型T的右值引用(其中T是引用類型)成爲類型T的引用。

(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&

考慮最簡單的模板功能-最小和最大。 在C ++ 03中,您必須手動重載const和非const的全部四個組合。 在C ++ 0x中,這只是一個重載。 結合可變參數模板,能夠實現完美的轉發。

template<typename A, typename B> auto min(A&& aref, B&& bref) {
    // for example, if you pass a const std::string& as first argument,
    // then A becomes const std::string& and by extension, aref becomes
    // const std::string&, completely maintaining it's type information.
    if (std::forward<A>(aref) < std::forward<B>(bref))
        return std::forward<A>(aref);
    else
        return std::forward<B>(bref);
}

我省去了返回類型推導,由於我不記得它是如何完成的,可是min能夠接受左值,右值,常量左值的任意組合。

相關文章
相關標籤/搜索