lvalue意思 - rvalue overloading c++




T &&(雙和號)在C++ 11中意味著什麼? (3)

T&& 與類型扣除 (如用於完美轉發) 一起使用的術語俗稱為通用參考 。 這是由Scott Meyers 在這篇文章中創造的。

那是因為它可能是r值或l值。

例子是:

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

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

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

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

請注意,標準本身沒有這個概念,它只是一種討論參考折疊規則,參考類型演繹和&&語法(單數?)組合的方法。

更多的討論可以在以下答案中找到: 通用引用的語法

我一直在研究C ++ 11的一些新特性,並且我注意到了在聲明變量時的雙和號,比如T&& var

一開始,這個野獸叫什麼? 我希望Google允許我們搜索這樣的標點符號。

這究竟意味著什麼?

乍一看,它似乎是一個雙引用(就像C風格的雙指針T** var ),但我很難想出一個用例。


右值引用是一種類似於普通引用X&的行為,除了幾個例外。 最重要的是,當涉及到函數重載解析時,左值更喜歡舊式的左值引用,而右值更喜歡新的右值引用:

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&&)

那麼什麼是右值? 任何不是左翼的。 左值是一個表達式,指的是一個內存位置,並允許我們通過&運算符來獲取該內存位置的地址。

首先通過一個例子來理解rvalues是如何完成的:

 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
};     

構造函數和賦值運算符已被重載的版本取右值引用。 Rvalue引用允許函數在編譯時(通過重載分辨率)在條件“我被調用左值還是右值嗎?”時分支。 這使我們能夠創建更高效的構造函數和賦值運算符,以便移動資源而不是複制它們。

編譯器會在編譯時自動分支(取決於它是被調用的左值還是右值),以選擇是否應該調用移動構造函數或移動賦值操作符。

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

一個實用的容易理解的例子是類模板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提供了一篇很好的文章來解釋所有這些和更多的內容(比如rvalues如何實現完美的轉發以及它的含義)。 這篇文章嚴重依賴他的文章。

簡短的介紹是由Stroutrup等人referencesreferences 。 人


它表示一個右值引用。 Rvalue引用只會綁定到臨時對象,除非另有明確的生成。 它們用於在特定情況下使對象更加高效,並提供稱為完美轉發的功能,這大大簡化了模板代碼。

在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可以合法存儲在標準容器中,排序等,而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和non-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可以接受任何左值,右值,常值左值的組合。





perfect-forwarding