[英]What does auto&& tell us?
如果您閱讀類似的代碼
auto&& var = foo();
其中foo
是按T
類型的值返回的任何函數。 則var
是對T
右值引用的左值。 但這對var
意味着什么? 這是否意味着我們被允許竊取var
的資源? 是否有任何合理的情況,當您使用auto&&
告訴代碼的讀者時,就像您返回unique_ptr<>
來告訴您具有獨占所有權一樣? 而當T
是類類型時,例如T&&
呢?
我只是想了解一下,除了模板編程中的auto&&
以外,還有其他用例auto&&
? 就像Scott Meyers在本文“ 通用參考”中的示例中所討論的一樣。
通過使用auto&& var = <initializer>
您的意思是: 我將接受任何初始化程序,無論它是左值表達式還是右值表達式,並且我將保留其constness 。 通常用於轉發 (通常與T&&
)。 之所以起作用,是因為“通用引用”( auto&&
或T&&
)將綁定到任何東西 。
你可能會說,好吧,為什么不直接使用const auto&
,因為這也將綁定到什么? 使用const
引用的問題在於它是const
! 您以后將無法將其綁定到任何非const引用或調用未標記為const
任何成員函數。
例如,假設您要獲取std::vector
,將迭代器帶到其第一個元素,然后以某種方式修改該迭代器指向的值:
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
無論初始化表達式如何,此代碼都可以正常編譯。 auto&&
的替代方法以下列方式失敗:
auto => will copy the vector, but we wanted a reference
auto& => will only bind to modifiable lvalues
const auto& => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues
因此, auto&&
可以完美運行! 像這樣使用auto&&
的示例是在基於范圍的for
循環中。 有關更多詳細信息,請參見我的其他問題 。
如果然后在auto&&
引用上使用std::forward
保留原來是左值或右值的事實,則代碼會說: 現在,我已經從左值或右值表達式中獲取了對象,我想保留其最初具有的任何價值,以便我可以最有效地使用它-這可能會使它失效。 如:
auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));
當原始的初始值設定項是可修改的右值時,這可以使use_it_elsewhere
其膽量以提高性能(避免復制)。
對於我們是否可以或何時可以從var
竊取資源,這意味着什么? 好吧,既然auto&&
將綁定到任何東西,我們就不可能嘗試自己擺脫var
guts-它很可能是左值甚至是const。 但是,我們可以將std::forward
傳遞給可能完全破壞其內部的其他函數。 一旦這樣做,我們應該考慮var
處於無效狀態。
現在,將其應用於auto&& var = foo();
,如您的問題中給出的那樣,其中foo通過值返回T
在這種情況下,我們肯定知道var
的類型將推導為T&&
。 由於我們可以肯定地知道它是一個右值,因此我們不需要std::forward
的許可來竊取其資源。 在這種特定情況下, 知道foo
按值返回 ,讀者應該將其讀取為: 我正在對foo
返回的臨時值進行右值引用,因此我可以很高興地從中移出。
作為附錄,我認為值得一提的是諸如some_expression_that_may_be_rvalue_or_lvalue
類的表達式可能出現,而不是出現“代碼可能會更改”的情況。 所以這是一個人為的例子:
std::vector<int> global_vec{1, 2, 3, 4};
template <typename T>
T get_vector()
{
return global_vec;
}
template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}
在這里, get_vector<T>()
是一個可愛的表達式,根據泛型T
,它可以是左值或右值。 我們實質上是通過foo
的template參數get_vector
更改get_vector
的返回類型。
當我們調用foo<std::vector<int>>
, get_vector
將按值返回global_vec
,從而給出一個右值表達式。 另外,當我們調用foo<std::vector<int>&>
, get_vector
將通過引用返回global_vec
,從而產生一個左值表達式。
如果這樣做:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
正如預期的那樣,我們得到以下輸出:
2
1
2
2
如果將代碼中的auto&&
更改為auto
, auto&
, const auto&
或const auto&&
則我們將無法獲得所需的結果。
根據是使用左值表達式還是右值表達式初始化auto&&
引用來更改程序邏輯的另一種方法是使用類型特征:
if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}
首先,我建議將我的答案作為側面閱讀,以逐步解釋通用引用的模板參數推導如何工作。
這是否意味着我們被允許竊取
var
的資源?
不必要。 如果foo()
突然返回了引用,或者您更改了調用但忘了更新var
的使用怎么辦? 或者,如果您使用的是通用代碼,並且foo()
的返回類型可能會根據您的參數而變化?
認為auto&&
與template<class T> void f(T&& v);
的T&&
完全相同template<class T> void f(T&& v);
,因為(幾乎是† )。 當您需要傳遞或以任何方式使用它們時,如何使用函數中的通用引用? 您使用std::forward<T>(v)
返回原始值類別。 如果在傳遞給函數之前是左值,則在通過std::forward
傳遞后將保持左值。 如果是右值,它將再次變為右值(請記住,命名的右值引用是左值)。
那么,如何以通用方式正確使用var
? 使用std::forward<decltype(var)>(var)
。 這將與上面的函數模板中的std::forward<T>(v)
完全相同。 如果var
是T&&
,則將返回右值;如果是T&
,則將返回左值。
因此,回到主題: auto&& v = f();
是什么? 代碼庫中的std::forward<decltype(v)>(v)
告訴我們嗎? 他們告訴我們,將以最有效的方式獲取和傳遞v
。 但是請記住,在轉發了這樣的變量之后,可能會將其移出,因此在不重新設置它的情況下進一步使用它是不正確的。
就個人而言,我用auto&&
在通用的代碼時,我需要一個modifyable變量。 完美轉發右值正在修改,因為移動操作可能會竊取其膽量。 如果我只是想變得懶惰(即即使我知道它也不要拼寫類型名稱)並且不需要修改(例如,僅打印范圍內的元素時),我會堅持使用auto const&
。
† auto
相差甚遠,因此auto v = {1,2,3};
將使v
成為std::initializer_list
,而f({1,2,3})
將是推論失敗。
考慮一些具有移動構造函數的類型T
,並假設
T t( foo() );
使用該move構造函數。
現在,讓我們使用一個中間引用來捕獲foo
的返回值:
auto const &ref = foo();
這會排除使用move構造函數,因此必須復制返回值,而不是移動它(即使我們在此處使用std::move
,我們也無法實際通過const ref進行移動)
T t(std::move(ref)); // invokes T::T(T const&)
但是,如果我們使用
auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)
move構造函數仍然可用。
並解決您的其他問題:
...在任何合理的情況下,您應該使用auto &&&告訴代碼的讀者...
正如Xeo所說,第一件事本質上是無論X是什么類型, 我都盡可能高效地傳遞X。 因此,看到內部使用auto&&
代碼應該傳達出在適當的地方內部將使用move語義的信息。
...就像您返回unique_ptr <>告訴您擁有專有所有權時一樣...
當函數模板接受類型為T&&
的參數時,就是說它可能會移動您傳入的對象。返回unique_ptr
顯式地賦予調用方所有權; 接受T&&
可能會刪除呼叫者的所有權(如果存在搬家公司等)。
auto &&
語法使用C ++ 11的兩個新功能:
auto
部分允許編譯器根據上下文(在這種情況下為返回值)推斷類型。 這沒有任何參考條件(允許您指定是否要對推導類型T
T
, T &
或T &&
)。
&&
是新的動作語義。 支持移動語義的類型實現了構造函數T(T && other)
,該構造函數可以最佳地移動新類型中的內容。 這允許對象交換內部表示,而不是執行深層復制。
這使您可以像:
std::vector<std::string> foo();
所以:
auto var = foo();
將執行返回的向量的副本(昂貴),但是:
auto &&var = foo();
將交換向量的內部表示形式(來自foo
的向量和來自var
的空向量),因此會更快。
這在新的for循環語法中使用:
for (auto &item : foo())
std::cout << item << std::endl;
其中,for循環將foo
和auto &&
保留為foo
的返回值,而item
是對foo
每個值的引用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.